@ugo-studio/jspp 0.3.1 → 0.3.3

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 (65) hide show
  1. package/dist/cli/args.js +22 -0
  2. package/dist/cli/compiler.js +53 -0
  3. package/dist/cli/index.js +43 -117
  4. package/dist/cli/pch.js +71 -0
  5. package/dist/cli/runner.js +23 -0
  6. package/dist/cli/spinner.js +27 -11
  7. package/dist/cli/transpiler.js +20 -0
  8. package/dist/cli/utils.js +25 -27
  9. package/dist/cli/wasm.js +70 -0
  10. package/dist/index.js +17 -6
  11. package/dist/{analysis → interpreter/analysis}/scope.js +34 -1
  12. package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +542 -3
  13. package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
  14. package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +2 -2
  15. package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +21 -9
  16. package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +3 -3
  17. package/dist/{core → interpreter/core}/codegen/expression-handlers.js +136 -82
  18. package/dist/{core → interpreter/core}/codegen/function-handlers.js +108 -61
  19. package/dist/{core → interpreter/core}/codegen/helpers.js +79 -11
  20. package/dist/interpreter/core/codegen/index.js +156 -0
  21. package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
  22. package/dist/{core → interpreter/core}/codegen/statement-handlers.js +39 -1
  23. package/package.json +6 -4
  24. package/scripts/precompile-headers.ts +71 -19
  25. package/scripts/setup-emsdk.ts +114 -0
  26. package/src/prelude/any_value.cpp +851 -599
  27. package/src/prelude/any_value.hpp +28 -30
  28. package/src/prelude/jspp.hpp +3 -1
  29. package/src/prelude/library/boolean.cpp +30 -0
  30. package/src/prelude/library/boolean.hpp +14 -0
  31. package/src/prelude/library/error.cpp +2 -2
  32. package/src/prelude/library/global.cpp +2 -0
  33. package/src/prelude/library/math.cpp +46 -43
  34. package/src/prelude/library/math.hpp +2 -0
  35. package/src/prelude/library/object.cpp +1 -1
  36. package/src/prelude/types.hpp +10 -9
  37. package/src/prelude/utils/access.hpp +2 -36
  38. package/src/prelude/utils/assignment_operators.hpp +136 -20
  39. package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
  40. package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
  41. package/src/prelude/utils/operators.hpp +123 -88
  42. package/src/prelude/utils/operators_native.hpp +360 -0
  43. package/src/prelude/utils/well_known_symbols.hpp +13 -13
  44. package/src/prelude/values/array.cpp +3 -3
  45. package/src/prelude/values/boolean.cpp +64 -0
  46. package/src/prelude/values/iterator.cpp +262 -210
  47. package/src/prelude/values/number.cpp +137 -92
  48. package/src/prelude/values/object.cpp +163 -122
  49. package/src/prelude/values/prototypes/boolean.hpp +24 -0
  50. package/src/prelude/values/prototypes/number.hpp +8 -1
  51. package/dist/cli/file-utils.js +0 -20
  52. package/dist/cli-utils/args.js +0 -59
  53. package/dist/cli-utils/colors.js +0 -9
  54. package/dist/cli-utils/file-utils.js +0 -20
  55. package/dist/cli-utils/spinner.js +0 -55
  56. package/dist/cli.js +0 -153
  57. package/dist/core/codegen/index.js +0 -88
  58. package/src/prelude/utils/operators_primitive.hpp +0 -337
  59. /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
  60. /package/dist/{ast → interpreter/ast}/types.js +0 -0
  61. /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
  62. /package/dist/{core → interpreter/core}/constants.js +0 -0
  63. /package/dist/{core → interpreter/core}/error.js +0 -0
  64. /package/dist/{core → interpreter/core}/parser.js +0 -0
  65. /package/dist/{core → interpreter/core}/traverser.js +0 -0
@@ -41,12 +41,495 @@ 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
+ }
50
533
  defineParameter(nameNode, p, type) {
51
534
  if (ts.isIdentifier(nameNode)) {
52
535
  this.scopeManager.define(nameNode.text, {
@@ -253,6 +736,9 @@ export class TypeAnalyzer {
253
736
  type: "function",
254
737
  isClosure: false,
255
738
  captures: new Map(),
739
+ assignments: !ts.isBlock(node.body)
740
+ ? [node.body]
741
+ : [],
256
742
  };
257
743
  this.functionTypeInfo.set(node, funcType);
258
744
  this.scopeManager.enterScope(node);
@@ -282,7 +768,8 @@ export class TypeAnalyzer {
282
768
  isClosure: false,
283
769
  captures: new Map(),
284
770
  declaration: node,
285
- needsHeapAllocation: true, // Added: Functions are always heap-allocated
771
+ needsHeapAllocation: true,
772
+ assignments: [],
286
773
  };
287
774
  this.functionTypeInfo.set(node, funcType);
288
775
  this.scopeManager.enterScope(node);
@@ -320,6 +807,7 @@ export class TypeAnalyzer {
320
807
  captures: new Map(),
321
808
  declaration: node,
322
809
  needsHeapAllocation: true,
810
+ assignments: [],
323
811
  };
324
812
  this.scopeManager.define(funcName, funcType);
325
813
  this.functionTypeInfo.set(node, funcType);
@@ -352,6 +840,7 @@ export class TypeAnalyzer {
352
840
  type: "function", // Classes are functions
353
841
  declaration: classNode,
354
842
  needsHeapAllocation: true,
843
+ assignments: [],
355
844
  };
356
845
  this.scopeManager.define(name, typeInfo);
357
846
  }
@@ -392,8 +881,8 @@ export class TypeAnalyzer {
392
881
  captures: new Map(),
393
882
  declaration: node,
394
883
  needsHeapAllocation: true,
395
- };
396
- // 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,
397
886
  // but we need to track them for captures.
398
887
  this.functionTypeInfo.set(node, funcType);
399
888
  this.scopeManager.enterScope(node);
@@ -418,6 +907,7 @@ 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);
@@ -477,6 +967,9 @@ export class TypeAnalyzer {
477
967
  declaration: node,
478
968
  isConst,
479
969
  needsHeapAllocation: needsHeap,
970
+ assignments: node.initializer
971
+ ? [node.initializer]
972
+ : [],
480
973
  };
481
974
  if (isBlockScoped) {
482
975
  this.scopeManager.define(name, typeInfo);
@@ -543,6 +1036,52 @@ export class TypeAnalyzer {
543
1036
  ts.SyntaxKind.LastAssignment;
544
1037
  if (isAssignment) {
545
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
+ }
546
1085
  }
547
1086
  }
548
1087
  },
@@ -35,7 +35,7 @@ export function visitClassDeclaration(node, context) {
35
35
  constructorLambda = this.generateWrappedLambda(this.generateLambdaComponents(constructor, {
36
36
  ...classContext,
37
37
  isInsideFunction: true,
38
- lambdaName: className,
38
+ functionName: className,
39
39
  }, { isClass: true }));
40
40
  }
41
41
  else {
@@ -476,13 +476,13 @@ export function visitSwitchStatement(node, context) {
476
476
  let condition = "";
477
477
  if (firstIf) {
478
478
  condition =
479
- `(${fallthroughVar} || jspp::is_strictly_equal_to_primitive(${switchValueVar}, ${caseExprCode}))`;
479
+ `(${fallthroughVar} || jspp::is_strictly_equal_to_native(${switchValueVar}, ${caseExprCode}))`;
480
480
  code += `${this.indent()}if ${condition} {\n`;
481
481
  firstIf = false;
482
482
  }
483
483
  else {
484
484
  condition =
485
- `(${fallthroughVar} || jspp::is_strictly_equal_to_primitive(${switchValueVar}, ${caseExprCode}))`;
485
+ `(${fallthroughVar} || jspp::is_strictly_equal_to_native(${switchValueVar}, ${caseExprCode}))`;
486
486
  code += `${this.indent()}if ${condition} {\n`;
487
487
  }
488
488
  this.indentationLevel++;