kimchilang 1.0.1
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/.github/workflows/ci.yml +66 -0
- package/README.md +1547 -0
- package/create-kimchi-app/README.md +44 -0
- package/create-kimchi-app/index.js +214 -0
- package/create-kimchi-app/package.json +22 -0
- package/editors/README.md +121 -0
- package/editors/sublime/KimchiLang.sublime-syntax +138 -0
- package/editors/vscode/README.md +90 -0
- package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +55 -0
- package/editors/vscode/src/extension.js +354 -0
- package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
- package/examples/api/client.km +36 -0
- package/examples/async_pipe.km +58 -0
- package/examples/basic.kimchi +109 -0
- package/examples/cli_framework/README.md +92 -0
- package/examples/cli_framework/calculator.km +61 -0
- package/examples/cli_framework/deploy.km +126 -0
- package/examples/cli_framework/greeter.km +26 -0
- package/examples/config.static +27 -0
- package/examples/config.static.js +10 -0
- package/examples/env_test.km +37 -0
- package/examples/fibonacci.kimchi +17 -0
- package/examples/greeter.km +15 -0
- package/examples/hello.js +1 -0
- package/examples/hello.kimchi +3 -0
- package/examples/js_interop.km +42 -0
- package/examples/logger_example.km +34 -0
- package/examples/memo_fibonacci.km +17 -0
- package/examples/myapp/lib/http.js +14 -0
- package/examples/myapp/lib/http.km +16 -0
- package/examples/myapp/main.km +16 -0
- package/examples/myapp/main_with_mock.km +42 -0
- package/examples/myapp/services/api.js +18 -0
- package/examples/myapp/services/api.km +18 -0
- package/examples/new_features.kimchi +52 -0
- package/examples/project_example.static +20 -0
- package/examples/readme_examples.km +240 -0
- package/examples/reduce_pattern_match.km +85 -0
- package/examples/regex_match.km +46 -0
- package/examples/sample.js +45 -0
- package/examples/sample.km +39 -0
- package/examples/secrets.static +35 -0
- package/examples/secrets.static.js +30 -0
- package/examples/shell-example.mjs +144 -0
- package/examples/shell_example.km +19 -0
- package/examples/stdlib_test.km +22 -0
- package/examples/test_example.km +69 -0
- package/examples/testing/README.md +88 -0
- package/examples/testing/http_client.km +18 -0
- package/examples/testing/math.km +48 -0
- package/examples/testing/math.test.km +93 -0
- package/examples/testing/user_service.km +29 -0
- package/examples/testing/user_service.test.km +72 -0
- package/examples/use-config.mjs +141 -0
- package/examples/use_config.km +13 -0
- package/install.sh +59 -0
- package/package.json +29 -0
- package/pantry/acorn/index.km +1 -0
- package/pantry/is_number/index.km +1 -0
- package/pantry/is_odd/index.km +2 -0
- package/project.static +6 -0
- package/src/cli.js +1245 -0
- package/src/generator.js +1241 -0
- package/src/index.js +141 -0
- package/src/js2km.js +568 -0
- package/src/lexer.js +822 -0
- package/src/linter.js +810 -0
- package/src/package-manager.js +307 -0
- package/src/parser.js +1876 -0
- package/src/static-parser.js +500 -0
- package/src/typechecker.js +950 -0
- package/stdlib/array.km +0 -0
- package/stdlib/bitwise.km +38 -0
- package/stdlib/console.km +49 -0
- package/stdlib/date.km +97 -0
- package/stdlib/function.km +44 -0
- package/stdlib/http.km +197 -0
- package/stdlib/http.md +333 -0
- package/stdlib/index.km +26 -0
- package/stdlib/json.km +17 -0
- package/stdlib/logger.js +114 -0
- package/stdlib/logger.km +104 -0
- package/stdlib/math.km +120 -0
- package/stdlib/object.km +41 -0
- package/stdlib/promise.km +33 -0
- package/stdlib/string.km +93 -0
- package/stdlib/testing.md +265 -0
- package/test/test.js +599 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
import { NodeType } from './parser.js';
|
|
2
|
+
|
|
3
|
+
// Type definitions
|
|
4
|
+
export const Type = {
|
|
5
|
+
Unknown: 'unknown',
|
|
6
|
+
Any: 'any',
|
|
7
|
+
Number: 'number',
|
|
8
|
+
String: 'string',
|
|
9
|
+
Boolean: 'boolean',
|
|
10
|
+
Null: 'null',
|
|
11
|
+
Array: 'array',
|
|
12
|
+
Object: 'object',
|
|
13
|
+
Function: 'function',
|
|
14
|
+
Void: 'void',
|
|
15
|
+
Enum: 'enum',
|
|
16
|
+
Module: 'module',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Global module type registry - stores the exported interface of each module
|
|
20
|
+
const moduleTypeRegistry = new Map();
|
|
21
|
+
|
|
22
|
+
class TypeError extends Error {
|
|
23
|
+
constructor(message, node) {
|
|
24
|
+
const line = node?.line || 1;
|
|
25
|
+
const column = node?.column || 1;
|
|
26
|
+
super(`Type Error at ${line}:${column}: ${message}`);
|
|
27
|
+
this.name = 'TypeError';
|
|
28
|
+
this.node = node;
|
|
29
|
+
this.line = line;
|
|
30
|
+
this.column = column;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class TypeChecker {
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.scopes = [new Map()]; // Stack of scopes
|
|
37
|
+
this.errors = [];
|
|
38
|
+
this.functions = new Map(); // Function signatures
|
|
39
|
+
this.enums = new Map(); // Enum definitions
|
|
40
|
+
this.modulePath = options.modulePath || null;
|
|
41
|
+
this.moduleExports = {}; // Track exposed declarations for this module
|
|
42
|
+
this.argTypes = new Map(); // Track arg declaration types
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Static methods for module type registry
|
|
46
|
+
static registerModuleType(modulePath, exportType) {
|
|
47
|
+
moduleTypeRegistry.set(modulePath, exportType);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static getModuleType(modulePath) {
|
|
51
|
+
return moduleTypeRegistry.get(modulePath) || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static clearRegistry() {
|
|
55
|
+
moduleTypeRegistry.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
check(ast) {
|
|
59
|
+
this.errors = [];
|
|
60
|
+
this.visitProgram(ast);
|
|
61
|
+
|
|
62
|
+
// Register this module's export type if we have a module path
|
|
63
|
+
if (this.modulePath && Object.keys(this.moduleExports).length > 0) {
|
|
64
|
+
TypeChecker.registerModuleType(this.modulePath, this.createObjectType(this.moduleExports));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return this.errors;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Scope management
|
|
71
|
+
pushScope() {
|
|
72
|
+
this.scopes.push(new Map());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
popScope() {
|
|
76
|
+
this.scopes.pop();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
defineVariable(name, typeInfo) {
|
|
80
|
+
this.scopes[this.scopes.length - 1].set(name, typeInfo);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
lookupVariable(name) {
|
|
84
|
+
for (let i = this.scopes.length - 1; i >= 0; i--) {
|
|
85
|
+
if (this.scopes[i].has(name)) {
|
|
86
|
+
return this.scopes[i].get(name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
addError(message, node) {
|
|
93
|
+
this.errors.push(new TypeError(message, node));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Type utilities
|
|
97
|
+
createType(kind, properties = {}) {
|
|
98
|
+
return { kind, ...properties };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
createObjectType(properties) {
|
|
102
|
+
return { kind: Type.Object, properties };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
createArrayType(elementType) {
|
|
106
|
+
return { kind: Type.Array, elementType };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
createFunctionType(params, returnType) {
|
|
110
|
+
return { kind: Type.Function, params, returnType };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
typeToString(type) {
|
|
114
|
+
if (!type) return 'unknown';
|
|
115
|
+
if (typeof type === 'string') return type;
|
|
116
|
+
if (type.kind === Type.Array) {
|
|
117
|
+
return `${this.typeToString(type.elementType)}[]`;
|
|
118
|
+
}
|
|
119
|
+
if (type.kind === Type.Object && type.properties) {
|
|
120
|
+
const props = Object.entries(type.properties)
|
|
121
|
+
.map(([k, v]) => `${k}: ${this.typeToString(v)}`)
|
|
122
|
+
.join(', ');
|
|
123
|
+
return `{ ${props} }`;
|
|
124
|
+
}
|
|
125
|
+
if (type.kind === Type.Function) {
|
|
126
|
+
return `(${type.params.map(p => this.typeToString(p)).join(', ')}) => ${this.typeToString(type.returnType)}`;
|
|
127
|
+
}
|
|
128
|
+
return type.kind || 'unknown';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if types are compatible
|
|
132
|
+
isCompatible(expected, actual) {
|
|
133
|
+
if (!expected || !actual) return true;
|
|
134
|
+
if (expected.kind === Type.Any || actual.kind === Type.Any) return true;
|
|
135
|
+
if (expected.kind === Type.Unknown || actual.kind === Type.Unknown) return true;
|
|
136
|
+
if (expected.kind === actual.kind) {
|
|
137
|
+
if (expected.kind === Type.Object) {
|
|
138
|
+
// Check that actual has all properties of expected
|
|
139
|
+
if (expected.properties && actual.properties) {
|
|
140
|
+
for (const [key, expectedType] of Object.entries(expected.properties)) {
|
|
141
|
+
if (!(key in actual.properties)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (!this.isCompatible(expectedType, actual.properties[key])) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// AST Visitors
|
|
156
|
+
visitProgram(node) {
|
|
157
|
+
// First pass: collect function and enum declarations
|
|
158
|
+
for (const stmt of node.body) {
|
|
159
|
+
if (stmt.type === NodeType.FunctionDeclaration) {
|
|
160
|
+
this.registerFunction(stmt);
|
|
161
|
+
} else if (stmt.type === NodeType.EnumDeclaration) {
|
|
162
|
+
this.registerEnum(stmt);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Second pass: type check all statements
|
|
167
|
+
for (const stmt of node.body) {
|
|
168
|
+
this.visitStatement(stmt);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
registerFunction(node) {
|
|
173
|
+
const params = node.params.map(p => ({
|
|
174
|
+
name: p.name || p.argument,
|
|
175
|
+
type: this.createType(Type.Any),
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
this.functions.set(node.name, {
|
|
179
|
+
params,
|
|
180
|
+
returnType: this.createType(Type.Unknown),
|
|
181
|
+
node,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
registerEnum(node) {
|
|
186
|
+
const members = {};
|
|
187
|
+
for (const member of node.members) {
|
|
188
|
+
members[member.name] = this.createType(Type.Number);
|
|
189
|
+
}
|
|
190
|
+
this.enums.set(node.name, {
|
|
191
|
+
kind: Type.Enum,
|
|
192
|
+
name: node.name,
|
|
193
|
+
members,
|
|
194
|
+
});
|
|
195
|
+
// Also define the enum as a variable
|
|
196
|
+
this.defineVariable(node.name, this.createObjectType(members));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
visitStatement(node) {
|
|
200
|
+
switch (node.type) {
|
|
201
|
+
case NodeType.DecDeclaration:
|
|
202
|
+
this.visitDecDeclaration(node);
|
|
203
|
+
break;
|
|
204
|
+
case NodeType.FunctionDeclaration:
|
|
205
|
+
this.visitFunctionDeclaration(node);
|
|
206
|
+
break;
|
|
207
|
+
case NodeType.EnumDeclaration:
|
|
208
|
+
// Already registered
|
|
209
|
+
break;
|
|
210
|
+
case NodeType.IfStatement:
|
|
211
|
+
this.visitIfStatement(node);
|
|
212
|
+
break;
|
|
213
|
+
case NodeType.WhileStatement:
|
|
214
|
+
this.visitWhileStatement(node);
|
|
215
|
+
break;
|
|
216
|
+
case NodeType.ForInStatement:
|
|
217
|
+
this.visitForInStatement(node);
|
|
218
|
+
break;
|
|
219
|
+
case NodeType.ReturnStatement:
|
|
220
|
+
this.visitReturnStatement(node);
|
|
221
|
+
break;
|
|
222
|
+
case NodeType.TryStatement:
|
|
223
|
+
this.visitTryStatement(node);
|
|
224
|
+
break;
|
|
225
|
+
case NodeType.ThrowStatement:
|
|
226
|
+
this.visitExpression(node.argument);
|
|
227
|
+
break;
|
|
228
|
+
case NodeType.PatternMatch:
|
|
229
|
+
this.visitPatternMatch(node);
|
|
230
|
+
break;
|
|
231
|
+
case NodeType.PrintStatement:
|
|
232
|
+
this.visitExpression(node.argument || node.expression);
|
|
233
|
+
break;
|
|
234
|
+
case NodeType.ExpressionStatement:
|
|
235
|
+
this.visitExpression(node.expression);
|
|
236
|
+
break;
|
|
237
|
+
case NodeType.BlockStatement:
|
|
238
|
+
this.pushScope();
|
|
239
|
+
for (const stmt of node.body) {
|
|
240
|
+
this.visitStatement(stmt);
|
|
241
|
+
}
|
|
242
|
+
this.popScope();
|
|
243
|
+
break;
|
|
244
|
+
case NodeType.DepStatement:
|
|
245
|
+
this.visitDepStatement(node);
|
|
246
|
+
break;
|
|
247
|
+
case NodeType.ArgDeclaration:
|
|
248
|
+
this.visitArgDeclaration(node);
|
|
249
|
+
break;
|
|
250
|
+
case NodeType.EnvDeclaration:
|
|
251
|
+
this.visitEnvDeclaration(node);
|
|
252
|
+
break;
|
|
253
|
+
case NodeType.JSBlock:
|
|
254
|
+
case NodeType.ShellBlock:
|
|
255
|
+
// JS/Shell blocks are opaque - no type checking inside
|
|
256
|
+
break;
|
|
257
|
+
case NodeType.TestBlock:
|
|
258
|
+
case NodeType.DescribeBlock:
|
|
259
|
+
case NodeType.ExpectStatement:
|
|
260
|
+
case NodeType.AssertStatement:
|
|
261
|
+
// Test constructs - visit their bodies if present
|
|
262
|
+
if (node.body && node.body.body) {
|
|
263
|
+
this.pushScope();
|
|
264
|
+
for (const stmt of node.body.body) {
|
|
265
|
+
this.visitStatement(stmt);
|
|
266
|
+
}
|
|
267
|
+
this.popScope();
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
case NodeType.BreakStatement:
|
|
271
|
+
case NodeType.ContinueStatement:
|
|
272
|
+
// No type checking needed
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
visitDepStatement(node) {
|
|
278
|
+
// Get the expected module type from registry
|
|
279
|
+
const expectedModuleType = TypeChecker.getModuleType(node.path);
|
|
280
|
+
|
|
281
|
+
// If we have overrides, validate them against the expected module interface
|
|
282
|
+
if (node.overrides && expectedModuleType) {
|
|
283
|
+
const overrideType = this.visitExpression(node.overrides);
|
|
284
|
+
|
|
285
|
+
if (overrideType && overrideType.kind === Type.Object && overrideType.properties) {
|
|
286
|
+
// Check each override property
|
|
287
|
+
for (const [key, valueType] of Object.entries(overrideType.properties)) {
|
|
288
|
+
// Skip dotted paths (dependency overrides like "foo.bar": mockFn)
|
|
289
|
+
if (key.includes('.')) continue;
|
|
290
|
+
|
|
291
|
+
// Check if this is overriding an arg or an exported member
|
|
292
|
+
if (expectedModuleType.properties && key in expectedModuleType.properties) {
|
|
293
|
+
const expectedType = expectedModuleType.properties[key];
|
|
294
|
+
if (!this.isCompatible(expectedType, valueType)) {
|
|
295
|
+
this.addError(
|
|
296
|
+
`Type mismatch for '${key}' in dependency '${node.path}': expected ${this.typeToString(expectedType)}, got ${this.typeToString(valueType)}`,
|
|
297
|
+
node
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Define the dependency alias in scope with the module's type
|
|
306
|
+
if (expectedModuleType) {
|
|
307
|
+
this.defineVariable(node.alias, expectedModuleType);
|
|
308
|
+
} else {
|
|
309
|
+
// Unknown module - use Any type
|
|
310
|
+
this.defineVariable(node.alias, this.createType(Type.Any));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
visitArgDeclaration(node) {
|
|
315
|
+
let argType = this.createType(Type.Any);
|
|
316
|
+
|
|
317
|
+
// Infer type from default value if present
|
|
318
|
+
if (node.defaultValue) {
|
|
319
|
+
argType = this.visitExpression(node.defaultValue);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Store arg type for module export
|
|
323
|
+
this.argTypes.set(node.name, {
|
|
324
|
+
type: argType,
|
|
325
|
+
required: node.required,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Define the arg in scope
|
|
329
|
+
this.defineVariable(node.name, argType);
|
|
330
|
+
|
|
331
|
+
// Add to module exports so other modules can validate against it
|
|
332
|
+
this.moduleExports[node.name] = argType;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
visitEnvDeclaration(node) {
|
|
336
|
+
// Env variables are always strings (or undefined if not set)
|
|
337
|
+
const envType = this.createType(Type.String);
|
|
338
|
+
|
|
339
|
+
// Define the env variable in scope
|
|
340
|
+
this.defineVariable(node.name, envType);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
visitDecDeclaration(node) {
|
|
344
|
+
const initType = this.visitExpression(node.init);
|
|
345
|
+
|
|
346
|
+
// Track exposed declarations for module export type
|
|
347
|
+
if (node.exposed) {
|
|
348
|
+
this.moduleExports[node.name] = initType;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (node.destructuring) {
|
|
352
|
+
if (node.pattern.type === NodeType.ObjectPattern) {
|
|
353
|
+
// Object destructuring
|
|
354
|
+
for (const prop of node.pattern.properties) {
|
|
355
|
+
let propType = this.createType(Type.Unknown);
|
|
356
|
+
if (initType && initType.kind === Type.Object && initType.properties) {
|
|
357
|
+
if (prop.key in initType.properties) {
|
|
358
|
+
propType = initType.properties[prop.key];
|
|
359
|
+
} else {
|
|
360
|
+
this.addError(
|
|
361
|
+
`Property '${prop.key}' does not exist on type ${this.typeToString(initType)}`,
|
|
362
|
+
node
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
this.defineVariable(prop.value, propType);
|
|
367
|
+
}
|
|
368
|
+
} else if (node.pattern.type === NodeType.ArrayPattern) {
|
|
369
|
+
// Array destructuring
|
|
370
|
+
for (const elem of node.pattern.elements) {
|
|
371
|
+
if (elem) {
|
|
372
|
+
let elemType = this.createType(Type.Unknown);
|
|
373
|
+
if (initType && initType.kind === Type.Array && initType.elementType) {
|
|
374
|
+
elemType = initType.elementType;
|
|
375
|
+
}
|
|
376
|
+
this.defineVariable(elem.name, elemType);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
this.defineVariable(node.name, initType);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
visitFunctionDeclaration(node) {
|
|
386
|
+
// Track exposed functions for module export type
|
|
387
|
+
if (node.exposed) {
|
|
388
|
+
const fnInfo = this.functions.get(node.name);
|
|
389
|
+
if (fnInfo) {
|
|
390
|
+
this.moduleExports[node.name] = this.createFunctionType(
|
|
391
|
+
fnInfo.params.map(p => p.type),
|
|
392
|
+
fnInfo.returnType
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.pushScope();
|
|
398
|
+
|
|
399
|
+
// Define parameters in scope
|
|
400
|
+
for (const param of node.params) {
|
|
401
|
+
const name = param.name || param.argument;
|
|
402
|
+
let paramType = this.createType(Type.Any);
|
|
403
|
+
|
|
404
|
+
// Infer type from default value if present
|
|
405
|
+
if (param.defaultValue) {
|
|
406
|
+
paramType = this.visitExpression(param.defaultValue);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
this.defineVariable(name, paramType);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Visit function body
|
|
413
|
+
if (node.body && node.body.body) {
|
|
414
|
+
for (const stmt of node.body.body) {
|
|
415
|
+
this.visitStatement(stmt);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
this.popScope();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
visitIfStatement(node) {
|
|
423
|
+
this.visitExpression(node.test);
|
|
424
|
+
|
|
425
|
+
this.pushScope();
|
|
426
|
+
if (node.consequent.body) {
|
|
427
|
+
for (const stmt of node.consequent.body) {
|
|
428
|
+
this.visitStatement(stmt);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
this.popScope();
|
|
432
|
+
|
|
433
|
+
if (node.alternate) {
|
|
434
|
+
this.pushScope();
|
|
435
|
+
if (node.alternate.body) {
|
|
436
|
+
for (const stmt of node.alternate.body) {
|
|
437
|
+
this.visitStatement(stmt);
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
this.visitStatement(node.alternate);
|
|
441
|
+
}
|
|
442
|
+
this.popScope();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
visitWhileStatement(node) {
|
|
447
|
+
this.visitExpression(node.test);
|
|
448
|
+
this.pushScope();
|
|
449
|
+
if (node.body.body) {
|
|
450
|
+
for (const stmt of node.body.body) {
|
|
451
|
+
this.visitStatement(stmt);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
this.popScope();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
visitForInStatement(node) {
|
|
458
|
+
this.pushScope();
|
|
459
|
+
const iterableType = this.visitExpression(node.iterable);
|
|
460
|
+
|
|
461
|
+
// Infer loop variable type from iterable
|
|
462
|
+
let elemType = this.createType(Type.Any);
|
|
463
|
+
if (iterableType) {
|
|
464
|
+
if (iterableType.kind === Type.Array && iterableType.elementType) {
|
|
465
|
+
elemType = iterableType.elementType;
|
|
466
|
+
} else if (iterableType.kind === Type.String) {
|
|
467
|
+
elemType = this.createType(Type.String);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
this.defineVariable(node.variable, elemType);
|
|
472
|
+
|
|
473
|
+
if (node.body.body) {
|
|
474
|
+
for (const stmt of node.body.body) {
|
|
475
|
+
this.visitStatement(stmt);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
this.popScope();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
visitReturnStatement(node) {
|
|
482
|
+
if (node.argument) {
|
|
483
|
+
this.visitExpression(node.argument);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
visitTryStatement(node) {
|
|
488
|
+
this.pushScope();
|
|
489
|
+
if (node.block.body) {
|
|
490
|
+
for (const stmt of node.block.body) {
|
|
491
|
+
this.visitStatement(stmt);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
this.popScope();
|
|
495
|
+
|
|
496
|
+
if (node.handler) {
|
|
497
|
+
this.pushScope();
|
|
498
|
+
if (node.handler.param) {
|
|
499
|
+
this.defineVariable(node.handler.param, this.createType(Type.Object));
|
|
500
|
+
}
|
|
501
|
+
if (node.handler.body.body) {
|
|
502
|
+
for (const stmt of node.handler.body.body) {
|
|
503
|
+
this.visitStatement(stmt);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
this.popScope();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (node.finalizer) {
|
|
510
|
+
this.pushScope();
|
|
511
|
+
if (node.finalizer.body) {
|
|
512
|
+
for (const stmt of node.finalizer.body) {
|
|
513
|
+
this.visitStatement(stmt);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
this.popScope();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
visitPatternMatch(node) {
|
|
521
|
+
for (const matchCase of node.cases) {
|
|
522
|
+
this.visitExpression(matchCase.test);
|
|
523
|
+
if (matchCase.consequent.body) {
|
|
524
|
+
this.pushScope();
|
|
525
|
+
for (const stmt of matchCase.consequent.body) {
|
|
526
|
+
this.visitStatement(stmt);
|
|
527
|
+
}
|
|
528
|
+
this.popScope();
|
|
529
|
+
} else {
|
|
530
|
+
this.visitStatement(matchCase.consequent);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
visitExpression(node) {
|
|
536
|
+
if (!node) return this.createType(Type.Unknown);
|
|
537
|
+
|
|
538
|
+
switch (node.type) {
|
|
539
|
+
case NodeType.Literal:
|
|
540
|
+
return this.visitLiteral(node);
|
|
541
|
+
case NodeType.Identifier:
|
|
542
|
+
return this.visitIdentifier(node);
|
|
543
|
+
case NodeType.BinaryExpression:
|
|
544
|
+
return this.visitBinaryExpression(node);
|
|
545
|
+
case NodeType.UnaryExpression:
|
|
546
|
+
return this.visitUnaryExpression(node);
|
|
547
|
+
case NodeType.CallExpression:
|
|
548
|
+
return this.visitCallExpression(node);
|
|
549
|
+
case NodeType.MemberExpression:
|
|
550
|
+
return this.visitMemberExpression(node);
|
|
551
|
+
case NodeType.ArrayExpression:
|
|
552
|
+
return this.visitArrayExpression(node);
|
|
553
|
+
case NodeType.ObjectExpression:
|
|
554
|
+
return this.visitObjectExpression(node);
|
|
555
|
+
case NodeType.ArrowFunctionExpression:
|
|
556
|
+
return this.visitArrowFunctionExpression(node);
|
|
557
|
+
case NodeType.ConditionalExpression:
|
|
558
|
+
return this.visitConditionalExpression(node);
|
|
559
|
+
case NodeType.AssignmentExpression:
|
|
560
|
+
return this.visitAssignmentExpression(node);
|
|
561
|
+
case NodeType.AwaitExpression:
|
|
562
|
+
return this.visitExpression(node.argument);
|
|
563
|
+
case NodeType.SpreadElement:
|
|
564
|
+
return this.visitExpression(node.argument);
|
|
565
|
+
case NodeType.RangeExpression:
|
|
566
|
+
return this.createArrayType(this.createType(Type.Number));
|
|
567
|
+
case NodeType.FlowExpression:
|
|
568
|
+
return this.visitFlowExpression(node);
|
|
569
|
+
case NodeType.RegexLiteral:
|
|
570
|
+
return this.createType(Type.Object); // RegExp is an object type
|
|
571
|
+
case NodeType.MatchExpression:
|
|
572
|
+
return this.visitMatchExpression(node);
|
|
573
|
+
default:
|
|
574
|
+
return this.createType(Type.Unknown);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
visitLiteral(node) {
|
|
579
|
+
if (node.isNumber) return this.createType(Type.Number);
|
|
580
|
+
if (node.isString) return this.createType(Type.String);
|
|
581
|
+
if (typeof node.value === 'boolean') return this.createType(Type.Boolean);
|
|
582
|
+
if (node.value === null) return this.createType(Type.Null);
|
|
583
|
+
if (typeof node.value === 'number') return this.createType(Type.Number);
|
|
584
|
+
if (typeof node.value === 'string') return this.createType(Type.String);
|
|
585
|
+
return this.createType(Type.Unknown);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
visitIdentifier(node) {
|
|
589
|
+
const varType = this.lookupVariable(node.name);
|
|
590
|
+
if (varType) return varType;
|
|
591
|
+
|
|
592
|
+
// Check if it's a function
|
|
593
|
+
if (this.functions.has(node.name)) {
|
|
594
|
+
const fn = this.functions.get(node.name);
|
|
595
|
+
return this.createFunctionType(fn.params.map(p => p.type), fn.returnType);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Check if it's an enum
|
|
599
|
+
if (this.enums.has(node.name)) {
|
|
600
|
+
return this.enums.get(node.name);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Built-in globals
|
|
604
|
+
const builtins = [
|
|
605
|
+
'console', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number', 'Boolean',
|
|
606
|
+
'Date', 'Promise', 'fetch', 'setTimeout', 'setInterval', 'clearTimeout',
|
|
607
|
+
'clearInterval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'encodeURI',
|
|
608
|
+
'decodeURI', 'encodeURIComponent', 'decodeURIComponent', 'Error', 'TypeError',
|
|
609
|
+
'RangeError', 'SyntaxError', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
610
|
+
'Symbol', 'Proxy', 'Reflect', 'Intl', 'undefined', 'null', 'NaN', 'Infinity',
|
|
611
|
+
'globalThis', 'process', 'Buffer', 'require', 'module', 'exports', '__dirname',
|
|
612
|
+
'__filename', '_pipe', '_range', '_deepFreeze', 'true', 'false'
|
|
613
|
+
];
|
|
614
|
+
if (builtins.includes(node.name)) {
|
|
615
|
+
return this.createType(Type.Any);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Report undefined identifier error
|
|
619
|
+
this.addError(`Undefined identifier '${node.name}'`, node);
|
|
620
|
+
|
|
621
|
+
return this.createType(Type.Unknown);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
visitBinaryExpression(node) {
|
|
625
|
+
const leftType = this.visitExpression(node.left);
|
|
626
|
+
const rightType = this.visitExpression(node.right);
|
|
627
|
+
|
|
628
|
+
const op = node.operator;
|
|
629
|
+
|
|
630
|
+
// Arithmetic operators
|
|
631
|
+
if (['+', '-', '*', '/', '%', '**'].includes(op)) {
|
|
632
|
+
// String concatenation
|
|
633
|
+
if (op === '+' && (leftType.kind === Type.String || rightType.kind === Type.String)) {
|
|
634
|
+
return this.createType(Type.String);
|
|
635
|
+
}
|
|
636
|
+
return this.createType(Type.Number);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Comparison operators
|
|
640
|
+
if (['===', '!==', '==', '!=', '<', '>', '<=', '>=', 'is', 'is not'].includes(op)) {
|
|
641
|
+
return this.createType(Type.Boolean);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Logical operators
|
|
645
|
+
if (['&&', '||'].includes(op)) {
|
|
646
|
+
return this.createType(Type.Boolean);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Bitwise operators
|
|
650
|
+
if (['&', '|', '^', '<<', '>>'].includes(op)) {
|
|
651
|
+
return this.createType(Type.Number);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return this.createType(Type.Unknown);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
visitUnaryExpression(node) {
|
|
658
|
+
this.visitExpression(node.argument);
|
|
659
|
+
|
|
660
|
+
if (node.operator === '!' || node.operator === 'not') {
|
|
661
|
+
return this.createType(Type.Boolean);
|
|
662
|
+
}
|
|
663
|
+
if (node.operator === '-' || node.operator === '~') {
|
|
664
|
+
return this.createType(Type.Number);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return this.createType(Type.Unknown);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
visitCallExpression(node) {
|
|
671
|
+
const calleeType = this.visitExpression(node.callee);
|
|
672
|
+
|
|
673
|
+
// Check if callee is callable
|
|
674
|
+
if (calleeType && calleeType.kind !== Type.Function && calleeType.kind !== Type.Any && calleeType.kind !== Type.Unknown) {
|
|
675
|
+
this.addError(
|
|
676
|
+
`Type '${this.typeToString(calleeType)}' is not callable`,
|
|
677
|
+
node
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Visit arguments
|
|
682
|
+
for (const arg of node.arguments) {
|
|
683
|
+
this.visitExpression(arg);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Return the function's return type if known
|
|
687
|
+
if (calleeType && calleeType.kind === Type.Function && calleeType.returnType) {
|
|
688
|
+
return calleeType.returnType;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Check for known function calls
|
|
692
|
+
if (node.callee.type === NodeType.Identifier) {
|
|
693
|
+
const fnInfo = this.functions.get(node.callee.name);
|
|
694
|
+
if (fnInfo) {
|
|
695
|
+
return fnInfo.returnType;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Handle method calls on known types
|
|
700
|
+
if (node.callee.type === NodeType.MemberExpression) {
|
|
701
|
+
const objType = this.visitExpression(node.callee.object);
|
|
702
|
+
const prop = node.callee.computed ? null : node.callee.property.name;
|
|
703
|
+
|
|
704
|
+
// Array methods
|
|
705
|
+
if (objType && objType.kind === Type.Array) {
|
|
706
|
+
if (['map', 'filter', 'find', 'some', 'every'].includes(prop)) {
|
|
707
|
+
if (prop === 'map') {
|
|
708
|
+
return this.createArrayType(this.createType(Type.Unknown));
|
|
709
|
+
}
|
|
710
|
+
if (prop === 'filter') {
|
|
711
|
+
return objType;
|
|
712
|
+
}
|
|
713
|
+
if (prop === 'find') {
|
|
714
|
+
return objType.elementType || this.createType(Type.Unknown);
|
|
715
|
+
}
|
|
716
|
+
if (['some', 'every'].includes(prop)) {
|
|
717
|
+
return this.createType(Type.Boolean);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (['reduce'].includes(prop)) {
|
|
721
|
+
return this.createType(Type.Unknown);
|
|
722
|
+
}
|
|
723
|
+
if (['push', 'pop', 'shift', 'unshift', 'splice'].includes(prop)) {
|
|
724
|
+
return this.createType(Type.Unknown);
|
|
725
|
+
}
|
|
726
|
+
if (['join'].includes(prop)) {
|
|
727
|
+
return this.createType(Type.String);
|
|
728
|
+
}
|
|
729
|
+
if (['length'].includes(prop)) {
|
|
730
|
+
return this.createType(Type.Number);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// String methods
|
|
735
|
+
if (objType && objType.kind === Type.String) {
|
|
736
|
+
if (['split', 'toChars', 'toLines'].includes(prop)) {
|
|
737
|
+
return this.createArrayType(this.createType(Type.String));
|
|
738
|
+
}
|
|
739
|
+
if (['trim', 'toLowerCase', 'toUpperCase', 'slice', 'substring', 'replace', 'capitalize'].includes(prop)) {
|
|
740
|
+
return this.createType(Type.String);
|
|
741
|
+
}
|
|
742
|
+
if (['length', 'indexOf', 'lastIndexOf'].includes(prop)) {
|
|
743
|
+
return this.createType(Type.Number);
|
|
744
|
+
}
|
|
745
|
+
if (['includes', 'startsWith', 'endsWith', 'isEmpty', 'isBlank'].includes(prop)) {
|
|
746
|
+
return this.createType(Type.Boolean);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return this.createType(Type.Unknown);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
visitMatchExpression(node) {
|
|
755
|
+
// Match expression: subject ~ /regex/ or subject ~ /regex/ => { body }
|
|
756
|
+
this.visitExpression(node.subject);
|
|
757
|
+
this.visitExpression(node.pattern);
|
|
758
|
+
|
|
759
|
+
if (node.body) {
|
|
760
|
+
// With body: return type depends on body
|
|
761
|
+
if (node.body.type === NodeType.BlockStatement) {
|
|
762
|
+
this.pushScope();
|
|
763
|
+
// Add $match to scope
|
|
764
|
+
this.defineVariable('$match', this.createArrayType(this.createType(Type.String)));
|
|
765
|
+
for (const stmt of node.body.body) {
|
|
766
|
+
this.visitStatement(stmt);
|
|
767
|
+
}
|
|
768
|
+
this.popScope();
|
|
769
|
+
} else {
|
|
770
|
+
return this.visitExpression(node.body);
|
|
771
|
+
}
|
|
772
|
+
return this.createType(Type.Unknown);
|
|
773
|
+
} else {
|
|
774
|
+
// Without body: returns string (first match) or null
|
|
775
|
+
return this.createType(Type.String);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
visitMemberExpression(node) {
|
|
780
|
+
const objType = this.visitExpression(node.object);
|
|
781
|
+
|
|
782
|
+
// Handle computed access (array indexing)
|
|
783
|
+
if (node.computed) {
|
|
784
|
+
const indexType = this.visitExpression(node.property);
|
|
785
|
+
|
|
786
|
+
if (objType && objType.kind === Type.Array) {
|
|
787
|
+
return objType.elementType || this.createType(Type.Unknown);
|
|
788
|
+
}
|
|
789
|
+
if (objType && objType.kind === Type.String) {
|
|
790
|
+
return this.createType(Type.String);
|
|
791
|
+
}
|
|
792
|
+
return this.createType(Type.Unknown);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Handle property access - property can be a string directly or an object
|
|
796
|
+
let propName;
|
|
797
|
+
if (typeof node.property === 'string') {
|
|
798
|
+
propName = node.property;
|
|
799
|
+
} else if (node.property && node.property.name) {
|
|
800
|
+
propName = node.property.name;
|
|
801
|
+
} else if (node.property && node.property.value) {
|
|
802
|
+
propName = node.property.value;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Check for property on object type
|
|
806
|
+
if (objType && objType.kind === Type.Object && objType.properties) {
|
|
807
|
+
if (propName in objType.properties) {
|
|
808
|
+
return objType.properties[propName];
|
|
809
|
+
}
|
|
810
|
+
// Only error if we have a known object shape and the property doesn't exist
|
|
811
|
+
if (Object.keys(objType.properties).length > 0) {
|
|
812
|
+
this.addError(
|
|
813
|
+
`Property '${propName}' does not exist on type ${this.typeToString(objType)}`,
|
|
814
|
+
node
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Check for enum member access
|
|
820
|
+
if (objType && objType.kind === Type.Enum && objType.members) {
|
|
821
|
+
if (propName in objType.members) {
|
|
822
|
+
return objType.members[propName];
|
|
823
|
+
}
|
|
824
|
+
this.addError(
|
|
825
|
+
`Property '${propName}' does not exist on enum '${objType.name}'`,
|
|
826
|
+
node
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Built-in properties
|
|
831
|
+
if (objType) {
|
|
832
|
+
if (objType.kind === Type.Array && propName === 'length') {
|
|
833
|
+
return this.createType(Type.Number);
|
|
834
|
+
}
|
|
835
|
+
if (objType.kind === Type.String && propName === 'length') {
|
|
836
|
+
return this.createType(Type.Number);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return this.createType(Type.Unknown);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
visitArrayExpression(node) {
|
|
844
|
+
if (node.elements.length === 0) {
|
|
845
|
+
return this.createArrayType(this.createType(Type.Unknown));
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Infer element type from first element
|
|
849
|
+
const firstElemType = this.visitExpression(node.elements[0]);
|
|
850
|
+
|
|
851
|
+
// Visit all elements
|
|
852
|
+
for (let i = 1; i < node.elements.length; i++) {
|
|
853
|
+
this.visitExpression(node.elements[i]);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return this.createArrayType(firstElemType);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
visitObjectExpression(node) {
|
|
860
|
+
const properties = {};
|
|
861
|
+
|
|
862
|
+
for (const prop of node.properties) {
|
|
863
|
+
if (prop.type === NodeType.SpreadElement) {
|
|
864
|
+
const spreadType = this.visitExpression(prop.argument);
|
|
865
|
+
if (spreadType && spreadType.kind === Type.Object && spreadType.properties) {
|
|
866
|
+
Object.assign(properties, spreadType.properties);
|
|
867
|
+
}
|
|
868
|
+
} else {
|
|
869
|
+
// Key can be a string directly, or an object with name/value
|
|
870
|
+
let key;
|
|
871
|
+
if (typeof prop.key === 'string') {
|
|
872
|
+
key = prop.key;
|
|
873
|
+
} else if (prop.key && prop.key.name) {
|
|
874
|
+
key = prop.key.name;
|
|
875
|
+
} else if (prop.key && prop.key.value) {
|
|
876
|
+
key = prop.key.value;
|
|
877
|
+
}
|
|
878
|
+
const valueType = this.visitExpression(prop.value);
|
|
879
|
+
if (key) {
|
|
880
|
+
properties[key] = valueType;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
return this.createObjectType(properties);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
visitArrowFunctionExpression(node) {
|
|
889
|
+
this.pushScope();
|
|
890
|
+
|
|
891
|
+
// Define parameters
|
|
892
|
+
for (const param of node.params) {
|
|
893
|
+
const name = param.name || param.argument || param;
|
|
894
|
+
this.defineVariable(name, this.createType(Type.Any));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Visit body
|
|
898
|
+
let returnType = this.createType(Type.Void);
|
|
899
|
+
if (node.body.type === NodeType.BlockStatement) {
|
|
900
|
+
for (const stmt of node.body.body) {
|
|
901
|
+
this.visitStatement(stmt);
|
|
902
|
+
}
|
|
903
|
+
} else {
|
|
904
|
+
returnType = this.visitExpression(node.body);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
this.popScope();
|
|
908
|
+
|
|
909
|
+
return this.createFunctionType(
|
|
910
|
+
node.params.map(() => this.createType(Type.Any)),
|
|
911
|
+
returnType
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
visitConditionalExpression(node) {
|
|
916
|
+
this.visitExpression(node.test);
|
|
917
|
+
const consequentType = this.visitExpression(node.consequent);
|
|
918
|
+
const alternateType = this.visitExpression(node.alternate);
|
|
919
|
+
|
|
920
|
+
// Return the type if both branches have the same type
|
|
921
|
+
if (consequentType.kind === alternateType.kind) {
|
|
922
|
+
return consequentType;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
return this.createType(Type.Unknown);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
visitAssignmentExpression(node) {
|
|
929
|
+
const valueType = this.visitExpression(node.right);
|
|
930
|
+
|
|
931
|
+
// Update variable type if it's a simple identifier
|
|
932
|
+
if (node.left.type === NodeType.Identifier) {
|
|
933
|
+
this.defineVariable(node.left.name, valueType);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return valueType;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
visitFlowExpression(node) {
|
|
940
|
+
let currentType = this.visitExpression(node.left);
|
|
941
|
+
|
|
942
|
+
// Flow expression has left and right, not initial and steps
|
|
943
|
+
if (node.right) {
|
|
944
|
+
this.visitExpression(node.right);
|
|
945
|
+
currentType = this.createType(Type.Unknown);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return currentType;
|
|
949
|
+
}
|
|
950
|
+
}
|