@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.
- package/dist/cli/args.js +22 -0
- package/dist/cli/compiler.js +53 -0
- package/dist/cli/index.js +43 -117
- package/dist/cli/pch.js +71 -0
- package/dist/cli/runner.js +23 -0
- package/dist/cli/spinner.js +27 -11
- package/dist/cli/transpiler.js +20 -0
- package/dist/cli/utils.js +25 -27
- package/dist/cli/wasm.js +70 -0
- package/dist/index.js +17 -6
- package/dist/{analysis → interpreter/analysis}/scope.js +34 -1
- package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +542 -3
- package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
- package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +2 -2
- package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +21 -9
- package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +3 -3
- package/dist/{core → interpreter/core}/codegen/expression-handlers.js +136 -82
- package/dist/{core → interpreter/core}/codegen/function-handlers.js +108 -61
- package/dist/{core → interpreter/core}/codegen/helpers.js +79 -11
- package/dist/interpreter/core/codegen/index.js +156 -0
- package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
- package/dist/{core → interpreter/core}/codegen/statement-handlers.js +39 -1
- package/package.json +6 -4
- package/scripts/precompile-headers.ts +71 -19
- package/scripts/setup-emsdk.ts +114 -0
- package/src/prelude/any_value.cpp +851 -599
- package/src/prelude/any_value.hpp +28 -30
- package/src/prelude/jspp.hpp +3 -1
- package/src/prelude/library/boolean.cpp +30 -0
- package/src/prelude/library/boolean.hpp +14 -0
- package/src/prelude/library/error.cpp +2 -2
- package/src/prelude/library/global.cpp +2 -0
- package/src/prelude/library/math.cpp +46 -43
- package/src/prelude/library/math.hpp +2 -0
- package/src/prelude/library/object.cpp +1 -1
- package/src/prelude/types.hpp +10 -9
- package/src/prelude/utils/access.hpp +2 -36
- package/src/prelude/utils/assignment_operators.hpp +136 -20
- package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
- package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
- package/src/prelude/utils/operators.hpp +123 -88
- package/src/prelude/utils/operators_native.hpp +360 -0
- package/src/prelude/utils/well_known_symbols.hpp +13 -13
- package/src/prelude/values/array.cpp +3 -3
- package/src/prelude/values/boolean.cpp +64 -0
- package/src/prelude/values/iterator.cpp +262 -210
- package/src/prelude/values/number.cpp +137 -92
- package/src/prelude/values/object.cpp +163 -122
- package/src/prelude/values/prototypes/boolean.hpp +24 -0
- package/src/prelude/values/prototypes/number.hpp +8 -1
- package/dist/cli/file-utils.js +0 -20
- package/dist/cli-utils/args.js +0 -59
- package/dist/cli-utils/colors.js +0 -9
- package/dist/cli-utils/file-utils.js +0 -20
- package/dist/cli-utils/spinner.js +0 -55
- package/dist/cli.js +0 -153
- package/dist/core/codegen/index.js +0 -88
- package/src/prelude/utils/operators_primitive.hpp +0 -337
- /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
- /package/dist/{ast → interpreter/ast}/types.js +0 -0
- /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
- /package/dist/{core → interpreter/core}/constants.js +0 -0
- /package/dist/{core → interpreter/core}/error.js +0 -0
- /package/dist/{core → interpreter/core}/parser.js +0 -0
- /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,
|
|
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
|
-
|
|
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::
|
|
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::
|
|
485
|
+
`(${fallthroughVar} || jspp::is_strictly_equal_to_native(${switchValueVar}, ${caseExprCode}))`;
|
|
486
486
|
code += `${this.indent()}if ${condition} {\n`;
|
|
487
487
|
}
|
|
488
488
|
this.indentationLevel++;
|