@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.
- package/LICENSE +25 -25
- package/README.md +20 -12
- package/dist/cli/args.js +22 -0
- package/dist/cli/compiler.js +53 -0
- package/dist/cli/index.js +43 -107
- 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 +59 -0
- package/dist/cli/wasm.js +70 -0
- package/dist/index.js +17 -6
- package/dist/{analysis → interpreter/analysis}/scope.js +38 -3
- package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +563 -28
- package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
- package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +12 -11
- package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +28 -9
- package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +9 -4
- package/dist/{core → interpreter/core}/codegen/expression-handlers.js +82 -88
- package/dist/{core → interpreter/core}/codegen/function-handlers.js +159 -46
- package/dist/{core → interpreter/core}/codegen/helpers.js +170 -25
- 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 +47 -7
- package/package.json +6 -4
- package/scripts/precompile-headers.ts +293 -50
- package/scripts/setup-compiler.ts +63 -63
- package/scripts/setup-emsdk.ts +114 -0
- package/src/prelude/any_value.cpp +888 -0
- package/src/prelude/any_value.hpp +29 -24
- package/src/prelude/{exception_helpers.hpp → exception.cpp} +53 -53
- package/src/prelude/exception.hpp +27 -27
- package/src/prelude/iterator_instantiations.hpp +10 -0
- package/src/prelude/{index.hpp → jspp.hpp} +13 -17
- package/src/prelude/library/array.cpp +191 -0
- package/src/prelude/library/array.hpp +5 -178
- package/src/prelude/library/boolean.cpp +30 -0
- package/src/prelude/library/boolean.hpp +14 -0
- package/src/prelude/library/console.cpp +125 -0
- package/src/prelude/library/console.hpp +9 -97
- package/src/prelude/library/error.cpp +100 -0
- package/src/prelude/library/error.hpp +8 -108
- package/src/prelude/library/function.cpp +69 -0
- package/src/prelude/library/function.hpp +6 -5
- package/src/prelude/library/global.cpp +98 -0
- package/src/prelude/library/global.hpp +12 -28
- package/src/prelude/library/global_usings.hpp +15 -0
- package/src/prelude/library/math.cpp +261 -0
- package/src/prelude/library/math.hpp +8 -288
- package/src/prelude/library/object.cpp +379 -0
- package/src/prelude/library/object.hpp +5 -267
- package/src/prelude/library/performance.cpp +21 -0
- package/src/prelude/library/performance.hpp +5 -20
- package/src/prelude/library/process.cpp +38 -0
- package/src/prelude/library/process.hpp +3 -31
- package/src/prelude/library/promise.cpp +131 -0
- package/src/prelude/library/promise.hpp +5 -116
- package/src/prelude/library/symbol.cpp +56 -0
- package/src/prelude/library/symbol.hpp +5 -46
- package/src/prelude/library/timer.cpp +88 -0
- package/src/prelude/library/timer.hpp +11 -87
- package/src/prelude/runtime.cpp +19 -0
- package/src/prelude/types.hpp +26 -20
- package/src/prelude/utils/access.hpp +123 -32
- package/src/prelude/utils/assignment_operators.hpp +119 -99
- package/src/prelude/utils/log_any_value/array.hpp +61 -40
- package/src/prelude/utils/log_any_value/function.hpp +39 -39
- package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
- package/src/prelude/utils/log_any_value/object.hpp +60 -3
- package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
- package/src/prelude/utils/operators.hpp +109 -94
- package/src/prelude/utils/operators_native.hpp +349 -0
- package/src/prelude/utils/well_known_symbols.hpp +24 -24
- package/src/prelude/values/array.cpp +1399 -0
- package/src/prelude/values/array.hpp +4 -0
- package/src/prelude/values/async_iterator.cpp +251 -0
- package/src/prelude/values/async_iterator.hpp +60 -32
- package/src/prelude/values/boolean.cpp +64 -0
- package/src/prelude/values/function.cpp +262 -0
- package/src/prelude/values/function.hpp +10 -30
- package/src/prelude/values/iterator.cpp +309 -0
- package/src/prelude/values/iterator.hpp +33 -64
- package/src/prelude/values/number.cpp +221 -0
- package/src/prelude/values/object.cpp +200 -0
- package/src/prelude/values/object.hpp +4 -0
- package/src/prelude/values/promise.cpp +479 -0
- package/src/prelude/values/promise.hpp +9 -2
- package/src/prelude/values/prototypes/array.hpp +46 -1348
- package/src/prelude/values/prototypes/async_iterator.hpp +19 -61
- package/src/prelude/values/prototypes/boolean.hpp +24 -0
- package/src/prelude/values/prototypes/function.hpp +7 -46
- package/src/prelude/values/prototypes/iterator.hpp +15 -191
- package/src/prelude/values/prototypes/number.hpp +30 -210
- package/src/prelude/values/prototypes/object.hpp +7 -23
- package/src/prelude/values/prototypes/promise.hpp +8 -186
- package/src/prelude/values/prototypes/string.hpp +28 -553
- package/src/prelude/values/prototypes/symbol.hpp +9 -70
- package/src/prelude/values/shape.hpp +52 -52
- package/src/prelude/values/string.cpp +485 -0
- package/src/prelude/values/symbol.cpp +89 -0
- package/src/prelude/values/symbol.hpp +101 -101
- 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 -86
- package/src/prelude/any_value_access.hpp +0 -170
- package/src/prelude/any_value_defines.hpp +0 -190
- package/src/prelude/any_value_helpers.hpp +0 -374
- package/src/prelude/utils/operators_primitive.hpp +0 -337
- package/src/prelude/values/helpers/array.hpp +0 -199
- package/src/prelude/values/helpers/async_iterator.hpp +0 -275
- package/src/prelude/values/helpers/function.hpp +0 -109
- package/src/prelude/values/helpers/iterator.hpp +0 -145
- package/src/prelude/values/helpers/object.hpp +0 -104
- package/src/prelude/values/helpers/promise.hpp +0 -254
- package/src/prelude/values/helpers/string.hpp +0 -37
- package/src/prelude/values/helpers/symbol.hpp +0 -21
- /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,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.
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
},
|