@ugo-studio/jspp 0.3.0 → 0.3.2

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.
Files changed (127) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +20 -12
  3. package/dist/cli/args.js +22 -0
  4. package/dist/cli/compiler.js +53 -0
  5. package/dist/cli/index.js +43 -107
  6. package/dist/cli/pch.js +71 -0
  7. package/dist/cli/runner.js +23 -0
  8. package/dist/cli/spinner.js +27 -11
  9. package/dist/cli/transpiler.js +20 -0
  10. package/dist/cli/utils.js +59 -0
  11. package/dist/cli/wasm.js +70 -0
  12. package/dist/index.js +17 -6
  13. package/dist/{analysis → interpreter/analysis}/scope.js +38 -3
  14. package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +563 -28
  15. package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
  16. package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +12 -11
  17. package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +28 -9
  18. package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +9 -4
  19. package/dist/{core → interpreter/core}/codegen/expression-handlers.js +82 -88
  20. package/dist/{core → interpreter/core}/codegen/function-handlers.js +159 -46
  21. package/dist/{core → interpreter/core}/codegen/helpers.js +170 -25
  22. package/dist/interpreter/core/codegen/index.js +156 -0
  23. package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
  24. package/dist/{core → interpreter/core}/codegen/statement-handlers.js +47 -7
  25. package/package.json +6 -4
  26. package/scripts/precompile-headers.ts +293 -50
  27. package/scripts/setup-compiler.ts +63 -63
  28. package/scripts/setup-emsdk.ts +114 -0
  29. package/src/prelude/any_value.cpp +888 -0
  30. package/src/prelude/any_value.hpp +29 -24
  31. package/src/prelude/{exception_helpers.hpp → exception.cpp} +53 -53
  32. package/src/prelude/exception.hpp +27 -27
  33. package/src/prelude/iterator_instantiations.hpp +10 -0
  34. package/src/prelude/{index.hpp → jspp.hpp} +13 -17
  35. package/src/prelude/library/array.cpp +191 -0
  36. package/src/prelude/library/array.hpp +5 -178
  37. package/src/prelude/library/boolean.cpp +30 -0
  38. package/src/prelude/library/boolean.hpp +14 -0
  39. package/src/prelude/library/console.cpp +125 -0
  40. package/src/prelude/library/console.hpp +9 -97
  41. package/src/prelude/library/error.cpp +100 -0
  42. package/src/prelude/library/error.hpp +8 -108
  43. package/src/prelude/library/function.cpp +69 -0
  44. package/src/prelude/library/function.hpp +6 -5
  45. package/src/prelude/library/global.cpp +98 -0
  46. package/src/prelude/library/global.hpp +12 -28
  47. package/src/prelude/library/global_usings.hpp +15 -0
  48. package/src/prelude/library/math.cpp +261 -0
  49. package/src/prelude/library/math.hpp +8 -288
  50. package/src/prelude/library/object.cpp +379 -0
  51. package/src/prelude/library/object.hpp +5 -267
  52. package/src/prelude/library/performance.cpp +21 -0
  53. package/src/prelude/library/performance.hpp +5 -20
  54. package/src/prelude/library/process.cpp +38 -0
  55. package/src/prelude/library/process.hpp +3 -31
  56. package/src/prelude/library/promise.cpp +131 -0
  57. package/src/prelude/library/promise.hpp +5 -116
  58. package/src/prelude/library/symbol.cpp +56 -0
  59. package/src/prelude/library/symbol.hpp +5 -46
  60. package/src/prelude/library/timer.cpp +88 -0
  61. package/src/prelude/library/timer.hpp +11 -87
  62. package/src/prelude/runtime.cpp +19 -0
  63. package/src/prelude/types.hpp +26 -20
  64. package/src/prelude/utils/access.hpp +123 -32
  65. package/src/prelude/utils/assignment_operators.hpp +119 -99
  66. package/src/prelude/utils/log_any_value/array.hpp +61 -40
  67. package/src/prelude/utils/log_any_value/function.hpp +39 -39
  68. package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
  69. package/src/prelude/utils/log_any_value/object.hpp +60 -3
  70. package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
  71. package/src/prelude/utils/operators.hpp +109 -94
  72. package/src/prelude/utils/operators_native.hpp +349 -0
  73. package/src/prelude/utils/well_known_symbols.hpp +24 -24
  74. package/src/prelude/values/array.cpp +1399 -0
  75. package/src/prelude/values/array.hpp +4 -0
  76. package/src/prelude/values/async_iterator.cpp +251 -0
  77. package/src/prelude/values/async_iterator.hpp +60 -32
  78. package/src/prelude/values/boolean.cpp +64 -0
  79. package/src/prelude/values/function.cpp +262 -0
  80. package/src/prelude/values/function.hpp +10 -30
  81. package/src/prelude/values/iterator.cpp +309 -0
  82. package/src/prelude/values/iterator.hpp +33 -64
  83. package/src/prelude/values/number.cpp +221 -0
  84. package/src/prelude/values/object.cpp +200 -0
  85. package/src/prelude/values/object.hpp +4 -0
  86. package/src/prelude/values/promise.cpp +479 -0
  87. package/src/prelude/values/promise.hpp +9 -2
  88. package/src/prelude/values/prototypes/array.hpp +46 -1348
  89. package/src/prelude/values/prototypes/async_iterator.hpp +19 -61
  90. package/src/prelude/values/prototypes/boolean.hpp +24 -0
  91. package/src/prelude/values/prototypes/function.hpp +7 -46
  92. package/src/prelude/values/prototypes/iterator.hpp +15 -191
  93. package/src/prelude/values/prototypes/number.hpp +30 -210
  94. package/src/prelude/values/prototypes/object.hpp +7 -23
  95. package/src/prelude/values/prototypes/promise.hpp +8 -186
  96. package/src/prelude/values/prototypes/string.hpp +28 -553
  97. package/src/prelude/values/prototypes/symbol.hpp +9 -70
  98. package/src/prelude/values/shape.hpp +52 -52
  99. package/src/prelude/values/string.cpp +485 -0
  100. package/src/prelude/values/symbol.cpp +89 -0
  101. package/src/prelude/values/symbol.hpp +101 -101
  102. package/dist/cli/file-utils.js +0 -20
  103. package/dist/cli-utils/args.js +0 -59
  104. package/dist/cli-utils/colors.js +0 -9
  105. package/dist/cli-utils/file-utils.js +0 -20
  106. package/dist/cli-utils/spinner.js +0 -55
  107. package/dist/cli.js +0 -153
  108. package/dist/core/codegen/index.js +0 -86
  109. package/src/prelude/any_value_access.hpp +0 -170
  110. package/src/prelude/any_value_defines.hpp +0 -190
  111. package/src/prelude/any_value_helpers.hpp +0 -374
  112. package/src/prelude/utils/operators_primitive.hpp +0 -337
  113. package/src/prelude/values/helpers/array.hpp +0 -199
  114. package/src/prelude/values/helpers/async_iterator.hpp +0 -275
  115. package/src/prelude/values/helpers/function.hpp +0 -109
  116. package/src/prelude/values/helpers/iterator.hpp +0 -145
  117. package/src/prelude/values/helpers/object.hpp +0 -104
  118. package/src/prelude/values/helpers/promise.hpp +0 -254
  119. package/src/prelude/values/helpers/string.hpp +0 -37
  120. package/src/prelude/values/helpers/symbol.hpp +0 -21
  121. /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
  122. /package/dist/{ast → interpreter/ast}/types.js +0 -0
  123. /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
  124. /package/dist/{core → interpreter/core}/constants.js +0 -0
  125. /package/dist/{core → interpreter/core}/error.js +0 -0
  126. /package/dist/{core → interpreter/core}/parser.js +0 -0
  127. /package/dist/{core → interpreter/core}/traverser.js +0 -0
@@ -41,12 +41,511 @@ function getParameterType(node) {
41
41
  export class TypeAnalyzer {
42
42
  traverser = new Traverser();
43
43
  scopeManager = new ScopeManager();
44
+ callSites = new Map();
44
45
  functionTypeInfo = new Map();
45
46
  functionStack = [];
46
47
  nodeToScope = new Map();
47
48
  labelStack = [];
48
49
  loopDepth = 0;
49
50
  switchDepth = 0;
51
+ /**
52
+ * Resolve the scope for a node by walking up the parent chain to find
53
+ * the nearest ancestor that has a mapped scope entry.
54
+ */
55
+ resolveScope(node) {
56
+ let current = node;
57
+ while (current) {
58
+ const mapped = this.nodeToScope.get(current);
59
+ if (mapped)
60
+ return mapped;
61
+ current = current.parent;
62
+ }
63
+ return this.scopeManager.currentScope;
64
+ }
65
+ /**
66
+ * Walk up the AST to find the containing function-like declaration.
67
+ */
68
+ findContainingFunction(node) {
69
+ let current = node.parent;
70
+ while (current) {
71
+ if (ts.isFunctionDeclaration(current) ||
72
+ ts.isFunctionExpression(current) ||
73
+ ts.isArrowFunction(current) ||
74
+ ts.isMethodDeclaration(current) ||
75
+ ts.isConstructorDeclaration(current)) {
76
+ return current;
77
+ }
78
+ current = current.parent;
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Check if a call expression is inside a given function (i.e. it's a recursive/internal call).
84
+ */
85
+ isCallInsideFunction(call, func) {
86
+ let current = call.parent;
87
+ while (current) {
88
+ if (current === func)
89
+ return true;
90
+ current = current.parent;
91
+ }
92
+ return false;
93
+ }
94
+ /**
95
+ * Infer the type of a parameter by looking at external (non-recursive) call sites.
96
+ * Returns null if no external call sites found or types are indeterminate.
97
+ */
98
+ inferParameterTypeFromCallSites(paramDecl, scope, visited) {
99
+ const containingFunc = this.findContainingFunction(paramDecl);
100
+ if (!containingFunc)
101
+ return null;
102
+ // Find the parameter index
103
+ const paramIndex = containingFunc
104
+ .parameters.indexOf(paramDecl);
105
+ if (paramIndex < 0)
106
+ return null;
107
+ // Get call sites for this function
108
+ const sites = this.callSites.get(containingFunc);
109
+ if (!sites || sites.length === 0)
110
+ return null;
111
+ // Filter to external call sites only (not recursive calls from within the function)
112
+ const externalSites = sites.filter((site) => !this.isCallInsideFunction(site, containingFunc));
113
+ if (externalSites.length === 0)
114
+ return null;
115
+ const types = [];
116
+ for (const site of externalSites) {
117
+ const arg = site.arguments[paramIndex];
118
+ if (arg) {
119
+ types.push(this.inferNodeReturnType(arg, scope, new Set(visited)));
120
+ }
121
+ }
122
+ const uniqueTypes = new Set(types.filter((t) => t !== "any"));
123
+ if (uniqueTypes.size === 1) {
124
+ return uniqueTypes.values().next().value;
125
+ }
126
+ if (uniqueTypes.size > 1)
127
+ return "any";
128
+ return null; // all were "any" or no arguments
129
+ }
130
+ inferFunctionReturnType(node, scope, visited = new Set(), typeEnv) {
131
+ if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) &&
132
+ !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node) &&
133
+ !ts.isConstructorDeclaration(node)) {
134
+ return "any";
135
+ }
136
+ // Create a unique key for the visit based on node and environment to allow context-sensitive recursion
137
+ const envKey = typeEnv
138
+ ? Array.from(typeEnv.entries()).map(([p, e]) => `${p.pos}:${e.pos}`)
139
+ .join(",")
140
+ : "no-env";
141
+ const visitKey = `func:${node.pos}:${envKey}`;
142
+ if (visited.has(visitKey))
143
+ return "any";
144
+ const newVisited = new Set(visited);
145
+ newVisited.add(visitKey);
146
+ const info = this.functionTypeInfo.get(node);
147
+ const internalScope = this.nodeToScope.get(node) || scope ||
148
+ this.scopeManager.currentScope;
149
+ // When called with a specific type environment (from a call site), use assignments analysis
150
+ // to determine the return type for this particular instantiation.
151
+ if (typeEnv) {
152
+ if (info && info.assignments && info.assignments.length > 0) {
153
+ const types = info.assignments.map((expr) => this.inferNodeReturnType(expr, internalScope, newVisited, typeEnv));
154
+ const uniqueTypes = new Set(types.filter((t) => t !== "any"));
155
+ if (uniqueTypes.size === 1) {
156
+ return uniqueTypes.values().next().value;
157
+ }
158
+ if (uniqueTypes.size > 1)
159
+ return "any";
160
+ }
161
+ }
162
+ // When called without typeEnv (top-level inference), prefer call-site analysis over
163
+ // raw assignments. Call-site analysis instantiates the function with actual argument
164
+ // types and is more precise — raw assignment analysis can't resolve parameter types
165
+ // and produces misleading "any" from recursion guards.
166
+ if (!typeEnv &&
167
+ (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) ||
168
+ ts.isArrowFunction(node))) {
169
+ const sites = this.callSites.get(node);
170
+ if (sites && sites.length > 0) {
171
+ // Only use external call sites — recursive calls create circular reasoning
172
+ const externalSites = sites.filter((site) => !this.isCallInsideFunction(site, node));
173
+ if (externalSites.length > 0) {
174
+ const callTypes = externalSites.map((site) => {
175
+ const env = new Map();
176
+ const params = node.parameters;
177
+ params.forEach((p, i) => {
178
+ if (site.arguments[i]) {
179
+ env.set(p, site.arguments[i]);
180
+ }
181
+ });
182
+ return this.inferFunctionReturnType(node, internalScope, new Set(newVisited), env);
183
+ });
184
+ // Don't filter "any" here — each external call site was instantiated with
185
+ // real arguments, so "any" means that instantiation genuinely returns mixed types.
186
+ const uniqueCallTypes = new Set(callTypes);
187
+ if (uniqueCallTypes.size === 1) {
188
+ return uniqueCallTypes.values().next().value;
189
+ }
190
+ if (uniqueCallTypes.size > 1)
191
+ return "any";
192
+ }
193
+ }
194
+ }
195
+ // Fallback: use assignments analysis without typeEnv (e.g. no call sites exist)
196
+ if (!typeEnv && info && info.assignments && info.assignments.length > 0) {
197
+ const types = info.assignments.map((expr) => this.inferNodeReturnType(expr, internalScope, newVisited, typeEnv));
198
+ const uniqueTypes = new Set(types.filter((t) => t !== "any"));
199
+ if (uniqueTypes.size === 1) {
200
+ return uniqueTypes.values().next().value;
201
+ }
202
+ if (uniqueTypes.size > 1)
203
+ return "any";
204
+ }
205
+ // Type checking TS signature
206
+ const signature = node;
207
+ if (signature.type) {
208
+ switch (signature.type.kind) {
209
+ case ts.SyntaxKind.StringKeyword:
210
+ return "string";
211
+ case ts.SyntaxKind.NumberKeyword:
212
+ return "number";
213
+ case ts.SyntaxKind.BooleanKeyword:
214
+ return "boolean";
215
+ case ts.SyntaxKind.ObjectKeyword:
216
+ return "object";
217
+ case ts.SyntaxKind.VoidKeyword:
218
+ return "any";
219
+ }
220
+ }
221
+ return "any";
222
+ }
223
+ inferNodeReturnType(node, scope, visited = new Set(), typeEnv) {
224
+ const envKey = typeEnv
225
+ ? Array.from(typeEnv.entries()).map(([p, e]) => `${p.pos}:${e.pos}`)
226
+ .join(",")
227
+ : "";
228
+ const visitKey = `node:${node.pos}:${node.kind}:${envKey}`;
229
+ if (visited.has(visitKey))
230
+ return "any";
231
+ const newVisited = new Set(visited);
232
+ newVisited.add(visitKey);
233
+ if (!scope) {
234
+ scope = this.resolveScope(node);
235
+ }
236
+ // 0a. Handle ParameterDeclaration nodes directly
237
+ if (ts.isParameter(node)) {
238
+ if (typeEnv) {
239
+ const arg = typeEnv.get(node);
240
+ if (arg) {
241
+ return this.inferNodeReturnType(arg, scope, newVisited, typeEnv);
242
+ }
243
+ }
244
+ // Infer from call sites
245
+ const callSiteType = this.inferParameterTypeFromCallSites(node, scope, newVisited);
246
+ if (callSiteType)
247
+ return callSiteType;
248
+ // Fall back to TS type annotation
249
+ if (node.type) {
250
+ switch (node.type.kind) {
251
+ case ts.SyntaxKind.StringKeyword:
252
+ return "string";
253
+ case ts.SyntaxKind.NumberKeyword:
254
+ return "number";
255
+ case ts.SyntaxKind.BooleanKeyword:
256
+ return "boolean";
257
+ case ts.SyntaxKind.ObjectKeyword:
258
+ return "object";
259
+ case ts.SyntaxKind.FunctionType:
260
+ return "function";
261
+ }
262
+ }
263
+ return "any";
264
+ }
265
+ // 0b. Check parameter environment for identifiers
266
+ if (ts.isIdentifier(node)) {
267
+ const definingScope = scope.findScopeFor(node.text);
268
+ const typeInfo = definingScope
269
+ ? definingScope.symbols.get(node.text)
270
+ : null;
271
+ if (typeInfo && typeInfo.isParameter && typeInfo.declaration &&
272
+ ts.isParameter(typeInfo.declaration)) {
273
+ const arg = typeEnv?.get(typeInfo.declaration);
274
+ if (arg) {
275
+ return this.inferNodeReturnType(arg, scope, newVisited, typeEnv);
276
+ }
277
+ // No typeEnv for this parameter — try call-site inference
278
+ const callSiteType = this.inferParameterTypeFromCallSites(typeInfo.declaration, scope, newVisited);
279
+ if (callSiteType)
280
+ return callSiteType;
281
+ }
282
+ }
283
+ if (ts.isVariableDeclarationList(node)) {
284
+ if (node.declarations.length > 0) {
285
+ return this.inferNodeReturnType(node.declarations[0], scope, newVisited, typeEnv);
286
+ }
287
+ return "any";
288
+ }
289
+ if (ts.isVariableDeclaration(node)) {
290
+ if (node.initializer) {
291
+ return this.inferNodeReturnType(node.initializer, scope, newVisited, typeEnv);
292
+ }
293
+ return "any";
294
+ }
295
+ // 1. Literal types
296
+ if (ts.isNumericLiteral(node))
297
+ return "number";
298
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
299
+ return "string";
300
+ if (node.kind === ts.SyntaxKind.TrueKeyword ||
301
+ node.kind === ts.SyntaxKind.FalseKeyword)
302
+ return "boolean";
303
+ if (node.kind === ts.SyntaxKind.NullKeyword ||
304
+ node.kind === ts.SyntaxKind.UndefinedKeyword)
305
+ return "any";
306
+ // 2. Complex literals / expressions
307
+ if (ts.isObjectLiteralExpression(node) ||
308
+ ts.isArrayLiteralExpression(node))
309
+ return "object";
310
+ if (ts.isFunctionExpression(node) || ts.isArrowFunction(node) ||
311
+ ts.isClassExpression(node))
312
+ return "function";
313
+ if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node)) {
314
+ return "function";
315
+ }
316
+ if (ts.isEnumDeclaration(node))
317
+ return "object";
318
+ // 3. Parenthesized / Await / Yield
319
+ if (ts.isParenthesizedExpression(node)) {
320
+ return this.inferNodeReturnType(node.expression, scope, newVisited, typeEnv);
321
+ }
322
+ if (ts.isAwaitExpression(node)) {
323
+ return this.inferNodeReturnType(node.expression, scope, newVisited, typeEnv);
324
+ }
325
+ if (ts.isYieldExpression(node)) {
326
+ return node.expression
327
+ ? this.inferNodeReturnType(node.expression, scope, newVisited, typeEnv)
328
+ : "any";
329
+ }
330
+ // 4. Unary
331
+ if (ts.isTypeOfExpression(node))
332
+ return "string";
333
+ if (ts.isVoidExpression(node))
334
+ return "any";
335
+ if (ts.isDeleteExpression(node))
336
+ return "boolean";
337
+ if (ts.isPrefixUnaryExpression(node)) {
338
+ switch (node.operator) {
339
+ case ts.SyntaxKind.ExclamationToken:
340
+ return "boolean";
341
+ case ts.SyntaxKind.PlusToken:
342
+ case ts.SyntaxKind.MinusToken:
343
+ case ts.SyntaxKind.TildeToken:
344
+ case ts.SyntaxKind.PlusPlusToken:
345
+ case ts.SyntaxKind.MinusMinusToken:
346
+ return "number";
347
+ }
348
+ }
349
+ if (ts.isPostfixUnaryExpression(node))
350
+ return "number";
351
+ // 5. Binary
352
+ if (ts.isBinaryExpression(node)) {
353
+ const op = node.operatorToken.kind;
354
+ // Assignment
355
+ if (op >= ts.SyntaxKind.FirstAssignment &&
356
+ op <= ts.SyntaxKind.LastAssignment) {
357
+ return this.inferNodeReturnType(node.right, scope, newVisited, typeEnv);
358
+ }
359
+ // Comparison
360
+ if ([
361
+ ts.SyntaxKind.EqualsEqualsToken,
362
+ ts.SyntaxKind.ExclamationEqualsToken,
363
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
364
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
365
+ ts.SyntaxKind.LessThanToken,
366
+ ts.SyntaxKind.LessThanEqualsToken,
367
+ ts.SyntaxKind.GreaterThanToken,
368
+ ts.SyntaxKind.GreaterThanEqualsToken,
369
+ ts.SyntaxKind.InKeyword,
370
+ ts.SyntaxKind.InstanceOfKeyword,
371
+ ].includes(op)) {
372
+ return "boolean";
373
+ }
374
+ // Arithmetic
375
+ if ([
376
+ ts.SyntaxKind.MinusToken,
377
+ ts.SyntaxKind.AsteriskToken,
378
+ ts.SyntaxKind.SlashToken,
379
+ ts.SyntaxKind.PercentToken,
380
+ ts.SyntaxKind.AsteriskAsteriskToken,
381
+ ts.SyntaxKind.LessThanLessThanToken,
382
+ ts.SyntaxKind.GreaterThanGreaterThanToken,
383
+ ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken,
384
+ ts.SyntaxKind.AmpersandToken,
385
+ ts.SyntaxKind.BarToken,
386
+ ts.SyntaxKind.CaretToken,
387
+ ].includes(op)) {
388
+ return "number";
389
+ }
390
+ // Plus
391
+ if (op === ts.SyntaxKind.PlusToken) {
392
+ const left = this.inferNodeReturnType(node.left, scope, newVisited, typeEnv);
393
+ const right = this.inferNodeReturnType(node.right, scope, newVisited, typeEnv);
394
+ if (left === "string" || right === "string")
395
+ return "string";
396
+ if (left === "number" && right === "number")
397
+ return "number";
398
+ return "any";
399
+ }
400
+ // Logical
401
+ if ([
402
+ ts.SyntaxKind.AmpersandAmpersandToken,
403
+ ts.SyntaxKind.BarBarToken,
404
+ ts.SyntaxKind.QuestionQuestionToken,
405
+ ].includes(op)) {
406
+ const left = this.inferNodeReturnType(node.left, scope, newVisited, typeEnv);
407
+ const right = this.inferNodeReturnType(node.right, scope, newVisited, typeEnv);
408
+ return left === right ? left : "any";
409
+ }
410
+ }
411
+ // 6. Conditional
412
+ if (ts.isConditionalExpression(node)) {
413
+ const trueType = this.inferNodeReturnType(node.whenTrue, scope, newVisited, typeEnv);
414
+ const falseType = this.inferNodeReturnType(node.whenFalse, scope, newVisited, typeEnv);
415
+ return trueType === falseType ? trueType : "any";
416
+ }
417
+ // 7. Template
418
+ if (ts.isTemplateExpression(node))
419
+ return "string";
420
+ // 8. New
421
+ if (ts.isNewExpression(node))
422
+ return "object";
423
+ // 9. Call
424
+ if (ts.isCallExpression(node)) {
425
+ const callee = node.expression;
426
+ if (ts.isIdentifier(callee)) {
427
+ const name = callee.text;
428
+ if (name === "Number" || name === "parseInt" ||
429
+ name === "parseFloat")
430
+ return "number";
431
+ if (name === "String")
432
+ return "string";
433
+ if (name === "Boolean" || name === "isNaN" ||
434
+ name === "isFinite")
435
+ return "boolean";
436
+ const typeInfo = this.scopeManager.lookupFromScope(name, scope);
437
+ if (typeInfo && typeInfo.declaration) {
438
+ const decl = typeInfo.declaration;
439
+ const env = new Map();
440
+ if (ts.isFunctionLike(decl)) {
441
+ decl.parameters.forEach((p, i) => {
442
+ if (node.arguments[i]) {
443
+ env.set(p, node.arguments[i]);
444
+ }
445
+ });
446
+ }
447
+ return this.inferFunctionReturnType(decl, scope, newVisited, env);
448
+ }
449
+ }
450
+ else if (ts.isPropertyAccessExpression(callee)) {
451
+ const obj = callee.expression.getText();
452
+ const prop = callee.name.text;
453
+ const full = `${obj}.${prop}`;
454
+ if (full.startsWith("Math.") && prop !== "PI" && prop !== "E") {
455
+ return "number";
456
+ }
457
+ if (full === "Array.isArray")
458
+ return "boolean";
459
+ if (full === "Object.keys" || full === "Object.values" ||
460
+ full === "Object.entries")
461
+ return "object";
462
+ }
463
+ return "any";
464
+ }
465
+ // 10. Identifier
466
+ if (ts.isIdentifier(node)) {
467
+ if (node.text === "NaN" || node.text === "Infinity") {
468
+ return "number";
469
+ }
470
+ if (node.text === "undefined")
471
+ return "any";
472
+ const typeInfo = this.scopeManager.lookupFromScope(node.text, scope);
473
+ if (typeInfo) {
474
+ // Check assignments
475
+ if (typeInfo.assignments && typeInfo.assignments.length > 0) {
476
+ const types = typeInfo.assignments.map((expr) => this.inferNodeReturnType(expr, scope, newVisited, typeEnv));
477
+ const uniqueTypes = new Set(types.filter((t) => t !== "any"));
478
+ if (uniqueTypes.size === 1) {
479
+ return uniqueTypes.values().next().value;
480
+ }
481
+ if (uniqueTypes.size > 1)
482
+ return "any";
483
+ }
484
+ // Check declaration for TS type
485
+ if (typeInfo.declaration &&
486
+ ts.isVariableDeclaration(typeInfo.declaration) &&
487
+ typeInfo.declaration.type) {
488
+ const kind = typeInfo.declaration.type.kind;
489
+ if (kind === ts.SyntaxKind.StringKeyword)
490
+ return "string";
491
+ if (kind === ts.SyntaxKind.NumberKeyword)
492
+ return "number";
493
+ if (kind === ts.SyntaxKind.BooleanKeyword)
494
+ return "boolean";
495
+ if (kind === ts.SyntaxKind.ObjectKeyword)
496
+ return "object";
497
+ }
498
+ // Builtin check
499
+ if (typeInfo.isBuiltin) {
500
+ if (["console", "Math", "process", "global", "globalThis"]
501
+ .includes(node.text))
502
+ return "object";
503
+ }
504
+ // Fallback to what we know
505
+ if (typeInfo.type === "string" || typeInfo.type === "number" ||
506
+ typeInfo.type === "boolean" || typeInfo.type === "object" ||
507
+ typeInfo.type === "function") {
508
+ return typeInfo.type;
509
+ }
510
+ if (typeInfo.type === "array")
511
+ return "object";
512
+ }
513
+ }
514
+ // 11. Property Access
515
+ if (ts.isPropertyAccessExpression(node)) {
516
+ if (node.name.text === "length") {
517
+ const objType = this.inferNodeReturnType(node.expression, scope, newVisited, typeEnv);
518
+ if (objType === "string" || objType === "object") {
519
+ return "number";
520
+ }
521
+ }
522
+ // Hard to know other properties without full type system
523
+ return "any";
524
+ }
525
+ // 12. Function / Method Return type inference
526
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) ||
527
+ ts.isArrowFunction(node) || ts.isMethodDeclaration(node) ||
528
+ ts.isConstructorDeclaration(node)) {
529
+ return this.inferFunctionReturnType(node, scope, newVisited, typeEnv);
530
+ }
531
+ return "any";
532
+ }
533
+ defineParameter(nameNode, p, type) {
534
+ if (ts.isIdentifier(nameNode)) {
535
+ this.scopeManager.define(nameNode.text, {
536
+ type: type || getParameterType(p),
537
+ isParameter: true,
538
+ declaration: p,
539
+ });
540
+ }
541
+ else {
542
+ nameNode.elements.forEach((element) => {
543
+ if (ts.isBindingElement(element)) {
544
+ this.defineParameter(element.name, p, type);
545
+ }
546
+ });
547
+ }
548
+ }
50
549
  analyze(ast) {
51
550
  this.nodeToScope.set(ast, this.scopeManager.currentScope);
52
551
  const crossScopeModificationVisitor = (node) => {
@@ -237,6 +736,9 @@ export class TypeAnalyzer {
237
736
  type: "function",
238
737
  isClosure: false,
239
738
  captures: new Map(),
739
+ assignments: !ts.isBlock(node.body)
740
+ ? [node.body]
741
+ : [],
240
742
  };
241
743
  this.functionTypeInfo.set(node, funcType);
242
744
  this.scopeManager.enterScope(node);
@@ -246,11 +748,7 @@ export class TypeAnalyzer {
246
748
  if (p.getText() == "this") { // Catch invalid parameters
247
749
  throw new CompilerError("Cannot use 'this' as a parameter name.", p, "SyntaxError");
248
750
  }
249
- this.scopeManager.define(p.name.getText(), {
250
- type: getParameterType(p),
251
- isParameter: true,
252
- declaration: p,
253
- });
751
+ this.defineParameter(p.name, p);
254
752
  });
255
753
  this.functionStack.push(node);
256
754
  }
@@ -270,7 +768,8 @@ export class TypeAnalyzer {
270
768
  isClosure: false,
271
769
  captures: new Map(),
272
770
  declaration: node,
273
- needsHeapAllocation: true, // Added: Functions are always heap-allocated
771
+ needsHeapAllocation: true,
772
+ assignments: [],
274
773
  };
275
774
  this.functionTypeInfo.set(node, funcType);
276
775
  this.scopeManager.enterScope(node);
@@ -284,11 +783,7 @@ export class TypeAnalyzer {
284
783
  if (p.getText() == "this") { // Catch invalid parameters
285
784
  throw new CompilerError("Cannot use 'this' as a parameter name.", p, "SyntaxError");
286
785
  }
287
- this.scopeManager.define(p.name.getText(), {
288
- type: getParameterType(p),
289
- isParameter: true,
290
- declaration: p,
291
- });
786
+ this.defineParameter(p.name, p);
292
787
  });
293
788
  this.functionStack.push(node);
294
789
  }
@@ -312,6 +807,7 @@ export class TypeAnalyzer {
312
807
  captures: new Map(),
313
808
  declaration: node,
314
809
  needsHeapAllocation: true,
810
+ assignments: [],
315
811
  };
316
812
  this.scopeManager.define(funcName, funcType);
317
813
  this.functionTypeInfo.set(node, funcType);
@@ -323,11 +819,7 @@ export class TypeAnalyzer {
323
819
  if (p.getText() == "this") { // Catch invalid parameters
324
820
  throw new CompilerError("Cannot use 'this' as a parameter name.", p, "SyntaxError");
325
821
  }
326
- this.scopeManager.define(p.name.getText(), {
327
- type: getParameterType(p),
328
- isParameter: true,
329
- declaration: p,
330
- });
822
+ this.defineParameter(p.name, p);
331
823
  });
332
824
  this.functionStack.push(node);
333
825
  }
@@ -348,6 +840,7 @@ export class TypeAnalyzer {
348
840
  type: "function", // Classes are functions
349
841
  declaration: classNode,
350
842
  needsHeapAllocation: true,
843
+ assignments: [],
351
844
  };
352
845
  this.scopeManager.define(name, typeInfo);
353
846
  }
@@ -388,17 +881,13 @@ export class TypeAnalyzer {
388
881
  captures: new Map(),
389
882
  declaration: node,
390
883
  needsHeapAllocation: true,
391
- };
392
- // Methods don't need to be defined in scope by name generally,
884
+ assignments: [],
885
+ }; // Methods don't need to be defined in scope by name generally,
393
886
  // but we need to track them for captures.
394
887
  this.functionTypeInfo.set(node, funcType);
395
888
  this.scopeManager.enterScope(node);
396
889
  this.nodeToScope.set(node, this.scopeManager.currentScope);
397
- node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
398
- type: "auto",
399
- isParameter: true,
400
- declaration: p,
401
- }));
890
+ node.parameters.forEach((p) => this.defineParameter(p.name, p, "auto"));
402
891
  this.functionStack.push(node);
403
892
  }
404
893
  },
@@ -418,15 +907,12 @@ export class TypeAnalyzer {
418
907
  captures: new Map(),
419
908
  declaration: node,
420
909
  needsHeapAllocation: true,
910
+ assignments: [],
421
911
  };
422
912
  this.functionTypeInfo.set(node, funcType);
423
913
  this.scopeManager.enterScope(node);
424
914
  this.nodeToScope.set(node, this.scopeManager.currentScope);
425
- node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
426
- type: "auto",
427
- isParameter: true,
428
- declaration: p,
429
- }));
915
+ node.parameters.forEach((p) => this.defineParameter(p.name, p, "auto"));
430
916
  this.functionStack.push(node); // Constructor acts like a function
431
917
  }
432
918
  },
@@ -481,6 +967,9 @@ export class TypeAnalyzer {
481
967
  declaration: node,
482
968
  isConst,
483
969
  needsHeapAllocation: needsHeap,
970
+ assignments: node.initializer
971
+ ? [node.initializer]
972
+ : [],
484
973
  };
485
974
  if (isBlockScoped) {
486
975
  this.scopeManager.define(name, typeInfo);
@@ -547,6 +1036,52 @@ export class TypeAnalyzer {
547
1036
  ts.SyntaxKind.LastAssignment;
548
1037
  if (isAssignment) {
549
1038
  crossScopeModificationVisitor(node.left);
1039
+ if (ts.isIdentifier(node.left)) {
1040
+ const name = node.left.text;
1041
+ const typeInfo = this.scopeManager.lookup(name);
1042
+ if (typeInfo) {
1043
+ if (!typeInfo.assignments) {
1044
+ typeInfo.assignments = [];
1045
+ }
1046
+ typeInfo.assignments.push(node.right);
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ },
1052
+ },
1053
+ CallExpression: {
1054
+ enter: (node) => {
1055
+ if (ts.isCallExpression(node)) {
1056
+ const callee = node.expression;
1057
+ if (ts.isIdentifier(callee)) {
1058
+ const name = callee.text;
1059
+ const typeInfo = this.scopeManager.lookup(name);
1060
+ if (typeInfo && typeInfo.declaration &&
1061
+ ts.isFunctionLike(typeInfo.declaration)) {
1062
+ const decl = typeInfo
1063
+ .declaration;
1064
+ if (!this.callSites.has(decl)) {
1065
+ this.callSites.set(decl, []);
1066
+ }
1067
+ this.callSites.get(decl).push(node);
1068
+ }
1069
+ }
1070
+ }
1071
+ },
1072
+ },
1073
+ ReturnStatement: {
1074
+ enter: (node) => {
1075
+ if (ts.isReturnStatement(node) && node.expression) {
1076
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1];
1077
+ if (currentFuncNode) {
1078
+ const info = this.functionTypeInfo.get(currentFuncNode);
1079
+ if (info) {
1080
+ if (!info.assignments) {
1081
+ info.assignments = [];
1082
+ }
1083
+ info.assignments.push(node.expression);
1084
+ }
550
1085
  }
551
1086
  }
552
1087
  },