stone-lang 0.1.0
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/README.md +52 -0
- package/StoneEngine.js +879 -0
- package/StoneEngineService.js +1727 -0
- package/adapters/FileSystemAdapter.js +230 -0
- package/adapters/OutputAdapter.js +208 -0
- package/adapters/index.js +6 -0
- package/cli/CLIOutputAdapter.js +196 -0
- package/cli/DaemonClient.js +349 -0
- package/cli/JSONOutputAdapter.js +135 -0
- package/cli/ReplSession.js +567 -0
- package/cli/ViewerServer.js +590 -0
- package/cli/commands/check.js +84 -0
- package/cli/commands/daemon.js +189 -0
- package/cli/commands/kill.js +66 -0
- package/cli/commands/package.js +713 -0
- package/cli/commands/ps.js +65 -0
- package/cli/commands/run.js +537 -0
- package/cli/entry.js +169 -0
- package/cli/index.js +14 -0
- package/cli/stonec.js +358 -0
- package/cli/test-compiler.js +181 -0
- package/cli/viewer/index.html +495 -0
- package/daemon/IPCServer.js +455 -0
- package/daemon/ProcessManager.js +327 -0
- package/daemon/ProcessRunner.js +307 -0
- package/daemon/daemon.js +398 -0
- package/daemon/index.js +16 -0
- package/frontend/analysis/index.js +5 -0
- package/frontend/analysis/livenessAnalyzer.js +568 -0
- package/frontend/analysis/treeShaker.js +265 -0
- package/frontend/index.js +20 -0
- package/frontend/parsing/astBuilder.js +2196 -0
- package/frontend/parsing/index.js +7 -0
- package/frontend/parsing/sonParser.js +592 -0
- package/frontend/parsing/stoneAstTypes.js +703 -0
- package/frontend/parsing/terminal-registry.js +435 -0
- package/frontend/parsing/tokenizer.js +692 -0
- package/frontend/type-checker/OverloadedFunctionType.js +43 -0
- package/frontend/type-checker/TypeEnvironment.js +165 -0
- package/frontend/type-checker/bidirectionalInference.js +149 -0
- package/frontend/type-checker/index.js +10 -0
- package/frontend/type-checker/moduleAnalysis.js +248 -0
- package/frontend/type-checker/operatorMappings.js +35 -0
- package/frontend/type-checker/overloadResolution.js +605 -0
- package/frontend/type-checker/typeChecker.js +452 -0
- package/frontend/type-checker/typeCompatibility.js +389 -0
- package/frontend/type-checker/visitors/controlFlow.js +483 -0
- package/frontend/type-checker/visitors/functions.js +604 -0
- package/frontend/type-checker/visitors/index.js +38 -0
- package/frontend/type-checker/visitors/literals.js +341 -0
- package/frontend/type-checker/visitors/modules.js +159 -0
- package/frontend/type-checker/visitors/operators.js +109 -0
- package/frontend/type-checker/visitors/statements.js +768 -0
- package/frontend/types/index.js +5 -0
- package/frontend/types/operatorMap.js +134 -0
- package/frontend/types/types.js +2046 -0
- package/frontend/utils/errorCollector.js +244 -0
- package/frontend/utils/index.js +5 -0
- package/frontend/utils/moduleResolver.js +479 -0
- package/package.json +50 -0
- package/packages/browserCache.js +359 -0
- package/packages/fetcher.js +236 -0
- package/packages/index.js +130 -0
- package/packages/lockfile.js +271 -0
- package/packages/manifest.js +291 -0
- package/packages/packageResolver.js +356 -0
- package/packages/resolver.js +310 -0
- package/packages/semver.js +635 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Literal Visitors - mixin for TypeChecker
|
|
3
|
+
*
|
|
4
|
+
* Visitors for literals, identifiers, arrays, objects, and binding structures.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ArrayType,
|
|
9
|
+
RecordType,
|
|
10
|
+
TypeVariable,
|
|
11
|
+
NUM,
|
|
12
|
+
BOOL,
|
|
13
|
+
STRING,
|
|
14
|
+
UNIT,
|
|
15
|
+
LiteralType,
|
|
16
|
+
freshTypeVar,
|
|
17
|
+
typeEquals,
|
|
18
|
+
isNumeric,
|
|
19
|
+
numericPromotion,
|
|
20
|
+
unify,
|
|
21
|
+
formatType,
|
|
22
|
+
fromTypeAnnotation,
|
|
23
|
+
} from "../../types/types.js";
|
|
24
|
+
import { OverloadedFunctionType } from "../OverloadedFunctionType.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Literal visitor methods to be mixed into TypeChecker.prototype
|
|
28
|
+
*/
|
|
29
|
+
export const literalVisitors = {
|
|
30
|
+
/**
|
|
31
|
+
* Visit a program
|
|
32
|
+
*/
|
|
33
|
+
visitProgram(node, env) {
|
|
34
|
+
for (const stmt of node.statements) {
|
|
35
|
+
this.visit(stmt, env);
|
|
36
|
+
}
|
|
37
|
+
return UNIT;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Main visitor dispatch
|
|
42
|
+
*/
|
|
43
|
+
visit(node, env) {
|
|
44
|
+
if (!node) return UNIT;
|
|
45
|
+
|
|
46
|
+
const methodName = `visit${node.type}`;
|
|
47
|
+
if (typeof this[methodName] === "function") {
|
|
48
|
+
return this[methodName](node, env);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Unknown node type - return fresh type variable
|
|
52
|
+
return this.setType(node, freshTypeVar());
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Visit a literal and infer its type
|
|
57
|
+
* @param {Object} node - Literal AST node
|
|
58
|
+
* @param {TypeEnvironment} env - Type environment
|
|
59
|
+
* @returns {Object} Inferred type
|
|
60
|
+
*
|
|
61
|
+
* For string and number literals, we infer LiteralType to enable
|
|
62
|
+
* compile-time checking against literal constraints.
|
|
63
|
+
* Example: "on" gets type LiteralType("on", "string"), not STRING
|
|
64
|
+
*/
|
|
65
|
+
visitLiteral(node, env) {
|
|
66
|
+
let type;
|
|
67
|
+
switch (node.literalType) {
|
|
68
|
+
case "number":
|
|
69
|
+
// Infer literal type for numbers to support literal constraints like level: 0 | 1 | 2
|
|
70
|
+
type = new LiteralType(node.value, "num");
|
|
71
|
+
break;
|
|
72
|
+
case "complex": {
|
|
73
|
+
// Complex is now a record type {re: num, im: num}
|
|
74
|
+
const fields = new Map();
|
|
75
|
+
fields.set("re", NUM);
|
|
76
|
+
fields.set("im", NUM);
|
|
77
|
+
type = new RecordType(fields, false);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "string":
|
|
81
|
+
// Infer literal type for strings to support literal constraints like mode: "on" | "off"
|
|
82
|
+
type = new LiteralType(node.value, "string");
|
|
83
|
+
break;
|
|
84
|
+
case "bool":
|
|
85
|
+
type = BOOL;
|
|
86
|
+
break;
|
|
87
|
+
case "none":
|
|
88
|
+
type = UNIT;
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
type = freshTypeVar();
|
|
92
|
+
}
|
|
93
|
+
return this.setType(node, type);
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Visit an identifier
|
|
98
|
+
*/
|
|
99
|
+
visitIdentifier(node, env) {
|
|
100
|
+
const type = env.lookup(node.name);
|
|
101
|
+
if (!type) {
|
|
102
|
+
// Check if this is a known overloaded primitive function
|
|
103
|
+
if (this.overloads[node.name]) {
|
|
104
|
+
// It's an overloaded primitive - create an OverloadedFunctionType
|
|
105
|
+
return this.setType(node, new OverloadedFunctionType(node.name, this.overloads[node.name]));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Unknown identifier - report error for undefined variable
|
|
109
|
+
this.addError(`Undefined variable: ${node.name}`, node.location);
|
|
110
|
+
|
|
111
|
+
// Still return a fresh type variable for error recovery
|
|
112
|
+
// This allows type checking to continue and catch other errors
|
|
113
|
+
return this.setType(node, freshTypeVar());
|
|
114
|
+
}
|
|
115
|
+
return this.setType(node, type);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Visit an array literal
|
|
120
|
+
* Always creates Array<T, R> type (lists are disabled for now)
|
|
121
|
+
*/
|
|
122
|
+
visitArrayLiteral(node, env) {
|
|
123
|
+
if (node.elements.length === 0) {
|
|
124
|
+
// Empty array - element type is inferred from usage (e.g., push operations)
|
|
125
|
+
return this.setType(node, new ArrayType(freshTypeVar(), 1));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Infer types of all elements
|
|
129
|
+
const elementTypes = node.elements.map((elem) => this.visit(elem, env));
|
|
130
|
+
|
|
131
|
+
// Check if rectangular (for Array type)
|
|
132
|
+
const { isRectangular, shape, baseType } = this.analyzeArrayStructure(
|
|
133
|
+
node,
|
|
134
|
+
elementTypes,
|
|
135
|
+
env
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (isRectangular && baseType && isNumeric(baseType)) {
|
|
139
|
+
// Dense numeric array: Array<T, R>
|
|
140
|
+
const rank = shape.length;
|
|
141
|
+
return this.setType(node, new ArrayType(baseType, rank));
|
|
142
|
+
} else {
|
|
143
|
+
// Non-rectangular or non-numeric - still create Array with inferred element type
|
|
144
|
+
// For now, treat as 1D array of the unified element type
|
|
145
|
+
const elemType = this.unifyTypes(elementTypes, node.location);
|
|
146
|
+
return this.setType(node, new ArrayType(elemType, 1));
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Analyze array structure to determine if rectangular
|
|
152
|
+
*/
|
|
153
|
+
analyzeArrayStructure(node, elementTypes, env) {
|
|
154
|
+
if (node.elements.length === 0) {
|
|
155
|
+
return { isRectangular: true, shape: [0], baseType: freshTypeVar() };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if all elements are the same type
|
|
159
|
+
const firstType = elementTypes[0];
|
|
160
|
+
|
|
161
|
+
// If first element is also an array, check nested structure
|
|
162
|
+
if (firstType instanceof ArrayType) {
|
|
163
|
+
// Check all elements have same rank and element type
|
|
164
|
+
for (const elemType of elementTypes) {
|
|
165
|
+
if (!(elemType instanceof ArrayType)) {
|
|
166
|
+
return { isRectangular: false, shape: null, baseType: null };
|
|
167
|
+
}
|
|
168
|
+
if (elemType.rank !== firstType.rank) {
|
|
169
|
+
return { isRectangular: false, shape: null, baseType: null };
|
|
170
|
+
}
|
|
171
|
+
if (!typeEquals(elemType.elementType, firstType.elementType)) {
|
|
172
|
+
return { isRectangular: false, shape: null, baseType: null };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Rectangular: total rank = 1 (outer) + inner rank
|
|
176
|
+
// Shape array needs enough elements to match total rank
|
|
177
|
+
const totalRank = 1 + firstType.rank;
|
|
178
|
+
const shape = new Array(totalRank).fill(node.elements.length);
|
|
179
|
+
return {
|
|
180
|
+
isRectangular: true,
|
|
181
|
+
shape: shape, // shape.length = totalRank
|
|
182
|
+
baseType: firstType.elementType,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Base case: all elements should be primitives
|
|
187
|
+
for (const elemType of elementTypes) {
|
|
188
|
+
if (elemType instanceof ArrayType) {
|
|
189
|
+
return { isRectangular: false, shape: null, baseType: null };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// All primitives - check if all numeric
|
|
194
|
+
const promotedType = this.unifyTypes(elementTypes, node.location);
|
|
195
|
+
if (isNumeric(promotedType)) {
|
|
196
|
+
return {
|
|
197
|
+
isRectangular: true,
|
|
198
|
+
shape: [node.elements.length],
|
|
199
|
+
baseType: promotedType,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { isRectangular: false, shape: null, baseType: promotedType };
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Unify multiple types into one
|
|
208
|
+
*/
|
|
209
|
+
unifyTypes(types, location) {
|
|
210
|
+
if (types.length === 0) return freshTypeVar();
|
|
211
|
+
if (types.length === 1) return types[0];
|
|
212
|
+
|
|
213
|
+
let result = types[0];
|
|
214
|
+
for (let i = 1; i < types.length; i++) {
|
|
215
|
+
const unifyResult = unify(result, types[i]);
|
|
216
|
+
if (!unifyResult.success) {
|
|
217
|
+
// Try numeric promotion
|
|
218
|
+
const promoted = numericPromotion(result, types[i]);
|
|
219
|
+
if (promoted) {
|
|
220
|
+
result = promoted;
|
|
221
|
+
} else {
|
|
222
|
+
// Heterogeneous arrays are not allowed
|
|
223
|
+
this.addError(
|
|
224
|
+
`Heterogeneous array: cannot mix ${formatType(result)} and ${formatType(types[i])}`,
|
|
225
|
+
location
|
|
226
|
+
);
|
|
227
|
+
result = types[i]; // Continue with last type for further checking
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
// Unification succeeded, use resolved type
|
|
231
|
+
if (result instanceof TypeVariable) {
|
|
232
|
+
result = result.resolve();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Visit an object literal
|
|
241
|
+
*/
|
|
242
|
+
visitObjectLiteral(node, env) {
|
|
243
|
+
// Empty object {} is an error
|
|
244
|
+
if (!node.properties || node.properties.length === 0) {
|
|
245
|
+
this.addError(
|
|
246
|
+
"Empty object literal {} is not allowed. Records must have at least one field.",
|
|
247
|
+
node.location
|
|
248
|
+
);
|
|
249
|
+
return this.setType(node, new RecordType(new Map(), false));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const fields = new Map();
|
|
253
|
+
for (const prop of node.properties) {
|
|
254
|
+
const valueType = this.visit(prop.value, env);
|
|
255
|
+
fields.set(prop.key, valueType);
|
|
256
|
+
}
|
|
257
|
+
return this.setType(node, new RecordType(fields, false));
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Visit a binding structure
|
|
262
|
+
* Can be either:
|
|
263
|
+
* 1. Object literal: { x = 1, y = 2 } → RecordType
|
|
264
|
+
* 2. Block with bindings: { x = 1, y = 2, trailingExpr } → type of trailingExpr
|
|
265
|
+
*/
|
|
266
|
+
visitBindingStructure(node, env) {
|
|
267
|
+
// Check if this is a block expression (has trailing expr or return statements)
|
|
268
|
+
const hasReturnBinding = node.bindings?.some(
|
|
269
|
+
(b) => !b.name && b.value && b.value.type === "ReturnStatement"
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
if (node.trailingExpr || hasReturnBinding) {
|
|
273
|
+
// Block with bindings that returns a value
|
|
274
|
+
const blockEnv = env.createChild();
|
|
275
|
+
|
|
276
|
+
// Type all bindings
|
|
277
|
+
for (const binding of node.bindings || []) {
|
|
278
|
+
if (binding.name) {
|
|
279
|
+
// Handle type descriptor bindings (may have null value)
|
|
280
|
+
if (binding.isTypeDescriptor) {
|
|
281
|
+
// Type descriptor: get type from annotation, optionally check default value
|
|
282
|
+
const declaredType = binding.typeAnnotation
|
|
283
|
+
? fromTypeAnnotation(binding.typeAnnotation, blockEnv)
|
|
284
|
+
: freshTypeVar();
|
|
285
|
+
if (binding.value) {
|
|
286
|
+
// Has default value - type check it
|
|
287
|
+
const valueType = this.visit(binding.value, blockEnv);
|
|
288
|
+
// Could add unification here to ensure default matches declared type
|
|
289
|
+
}
|
|
290
|
+
blockEnv.define(binding.name, declaredType);
|
|
291
|
+
} else if (binding.value) {
|
|
292
|
+
const valueType = this.visit(binding.value, blockEnv);
|
|
293
|
+
blockEnv.define(binding.name, valueType);
|
|
294
|
+
}
|
|
295
|
+
} else if (binding.value) {
|
|
296
|
+
// Effect (null name) - just type check for side effects
|
|
297
|
+
this.visit(binding.value, blockEnv);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Return type of trailing expression if present
|
|
302
|
+
if (node.trailingExpr) {
|
|
303
|
+
return this.setType(node, this.visit(node.trailingExpr, blockEnv));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return this.setType(node, UNIT);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// No trailing expression - this is an object literal (record)
|
|
310
|
+
const fields = new Map();
|
|
311
|
+
const requiredFields = new Set(); // Track type descriptors without defaults
|
|
312
|
+
let hasTypeDescriptors = false;
|
|
313
|
+
|
|
314
|
+
for (const binding of node.bindings || []) {
|
|
315
|
+
if (binding.name) {
|
|
316
|
+
// Handle type descriptor bindings (may have null value)
|
|
317
|
+
if (binding.isTypeDescriptor) {
|
|
318
|
+
hasTypeDescriptors = true;
|
|
319
|
+
// Type descriptor: get type from annotation
|
|
320
|
+
const declaredType = binding.typeAnnotation
|
|
321
|
+
? fromTypeAnnotation(binding.typeAnnotation, env)
|
|
322
|
+
: freshTypeVar();
|
|
323
|
+
if (binding.value) {
|
|
324
|
+
// Has default value - type check it
|
|
325
|
+
this.visit(binding.value, env);
|
|
326
|
+
} else {
|
|
327
|
+
// No default value - this is a required field
|
|
328
|
+
requiredFields.add(binding.name);
|
|
329
|
+
}
|
|
330
|
+
fields.set(binding.name, declaredType);
|
|
331
|
+
} else if (binding.value) {
|
|
332
|
+
const valueType = this.visit(binding.value, env);
|
|
333
|
+
fields.set(binding.name, valueType);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Only pass requiredFields if this is a schema record (has type descriptors)
|
|
338
|
+
const recordType = new RecordType(fields, false, false, hasTypeDescriptors ? requiredFields : null);
|
|
339
|
+
return this.setType(node, recordType);
|
|
340
|
+
},
|
|
341
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Visitors - mixin for TypeChecker
|
|
3
|
+
*
|
|
4
|
+
* Visitors for import/export statements, type aliases, and block expressions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
RecordType,
|
|
9
|
+
UNIT,
|
|
10
|
+
STRING,
|
|
11
|
+
freshTypeVar,
|
|
12
|
+
fromTypeAnnotation,
|
|
13
|
+
} from "../../types/types.js";
|
|
14
|
+
import { OverloadedFunctionType } from "../OverloadedFunctionType.js";
|
|
15
|
+
import { ExtensionInfo } from "../TypeEnvironment.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Module visitor methods to be mixed into TypeChecker.prototype
|
|
19
|
+
*/
|
|
20
|
+
export const moduleVisitors = {
|
|
21
|
+
/**
|
|
22
|
+
* Visit a type alias declaration
|
|
23
|
+
* type Name = TypeAnnotation
|
|
24
|
+
*/
|
|
25
|
+
visitTypeAlias(node, env) {
|
|
26
|
+
// Convert the type annotation to an internal type
|
|
27
|
+
const aliasedType = fromTypeAnnotation(node.typeAnnotation, env);
|
|
28
|
+
|
|
29
|
+
// Store in the environment's type alias registry
|
|
30
|
+
env.defineTypeAlias(node.name, aliasedType);
|
|
31
|
+
|
|
32
|
+
// Type alias declarations don't have a value, return unit
|
|
33
|
+
return this.setType(node, UNIT);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Visit an export statement
|
|
38
|
+
*/
|
|
39
|
+
visitExportStatement(node, env) {
|
|
40
|
+
return this.visit(node.declaration, env);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Visit an import statement
|
|
45
|
+
* Resolves types from source module when possible for static type checking.
|
|
46
|
+
* Reports errors for imports that don't exist in the module.
|
|
47
|
+
*/
|
|
48
|
+
visitImportStatement(node, env) {
|
|
49
|
+
// Register complex module overloads when any import from complex module is seen
|
|
50
|
+
// This allows operators like + - * / to work with complex numbers
|
|
51
|
+
if (node.modulePath === 'complex') {
|
|
52
|
+
this._registerComplexOverloads();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Try to get types from the source module
|
|
56
|
+
const sourceExports = this.getModuleExportTypes(node.modulePath);
|
|
57
|
+
const sourceExtensions = sourceExports?._extensions || new Set();
|
|
58
|
+
|
|
59
|
+
for (const spec of node.specifiers) {
|
|
60
|
+
// Support both spec.name (old format) and spec.importedName (new format)
|
|
61
|
+
const importedName = spec.importedName || spec.name;
|
|
62
|
+
const localName = spec.localName || importedName;
|
|
63
|
+
|
|
64
|
+
if (sourceExports) {
|
|
65
|
+
const sourceType = sourceExports.get(importedName);
|
|
66
|
+
if (sourceType) {
|
|
67
|
+
// If it's an overloaded function, register it for overload resolution
|
|
68
|
+
if (sourceType instanceof OverloadedFunctionType) {
|
|
69
|
+
// Register the overloads in our overload registry for this import
|
|
70
|
+
if (!this.overloads[localName]) {
|
|
71
|
+
this.overloads[localName] = sourceType.overloads;
|
|
72
|
+
}
|
|
73
|
+
// Store overloadIds on the specifier for the compiler to use
|
|
74
|
+
// This enables compile-time resolution of cross-module overloads
|
|
75
|
+
spec.overloadIds = sourceType.userOverloads.map(o => o.overloadId);
|
|
76
|
+
spec.isOverloaded = true;
|
|
77
|
+
}
|
|
78
|
+
env.define(localName, sourceType);
|
|
79
|
+
|
|
80
|
+
// If this import is an extension function, register it as such
|
|
81
|
+
if (sourceExtensions.has(importedName)) {
|
|
82
|
+
// For imported extensions, we create minimal ExtensionInfo
|
|
83
|
+
// The selfType info comes from the function's first parameter (already captured in sourceType)
|
|
84
|
+
env.defineExtension(localName, new ExtensionInfo(localName, null, sourceType));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Store type on specifier node for IDE hover support
|
|
88
|
+
this.setType(spec, sourceType);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// Module was analyzed but import name doesn't exist - report error
|
|
92
|
+
this.addError(
|
|
93
|
+
`'${importedName}' is not exported from module '${node.modulePath}'`,
|
|
94
|
+
spec.location || node.location
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
// Fall back to type variable to allow continued type checking
|
|
98
|
+
const typeVar = freshTypeVar();
|
|
99
|
+
env.define(localName, typeVar);
|
|
100
|
+
this.setType(spec, typeVar);
|
|
101
|
+
}
|
|
102
|
+
return UNIT;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Visit a namespace import statement
|
|
107
|
+
*/
|
|
108
|
+
visitNamespaceImportStatement(node, env) {
|
|
109
|
+
const alias = node.alias || node.modulePath.split("/").pop();
|
|
110
|
+
|
|
111
|
+
// Try to get the actual exports from the module
|
|
112
|
+
const sourceExports = this.getModuleExportTypes(node.modulePath);
|
|
113
|
+
|
|
114
|
+
if (sourceExports && sourceExports.size > 0) {
|
|
115
|
+
// Create a record type with all the module's exports as fields
|
|
116
|
+
const fields = new Map();
|
|
117
|
+
for (const [name, type] of sourceExports) {
|
|
118
|
+
fields.set(name, type);
|
|
119
|
+
// Register overloaded functions for resolution
|
|
120
|
+
if (type instanceof OverloadedFunctionType) {
|
|
121
|
+
// Store with qualified name for potential future use
|
|
122
|
+
const qualifiedName = `${alias}.${name}`;
|
|
123
|
+
if (!this.overloads[qualifiedName]) {
|
|
124
|
+
this.overloads[qualifiedName] = type.overloads;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
env.define(alias, new RecordType(fields, false));
|
|
129
|
+
} else {
|
|
130
|
+
// Fall back to open record if we can't analyze the module
|
|
131
|
+
env.define(alias, new RecordType(new Map(), true));
|
|
132
|
+
}
|
|
133
|
+
return UNIT;
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Visit a block expression
|
|
138
|
+
*/
|
|
139
|
+
visitBlockExpression(node, env) {
|
|
140
|
+
const blockEnv = env.createChild();
|
|
141
|
+
let lastType = UNIT;
|
|
142
|
+
|
|
143
|
+
for (const stmt of node.body) {
|
|
144
|
+
lastType = this.visit(stmt, blockEnv);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return this.setType(node, lastType);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Visit string interpolation
|
|
152
|
+
*/
|
|
153
|
+
visitStringInterpolation(node, env) {
|
|
154
|
+
for (const expr of node.expressions) {
|
|
155
|
+
this.visit(expr, env);
|
|
156
|
+
}
|
|
157
|
+
return this.setType(node, STRING);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator Visitors - mixin for TypeChecker
|
|
3
|
+
*
|
|
4
|
+
* Visitors for binary and unary operations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
BOOL,
|
|
9
|
+
freshTypeVar,
|
|
10
|
+
} from "../../types/types.js";
|
|
11
|
+
import { BINARY_OP_NAMES, UNARY_OP_NAMES } from "../operatorMappings.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Operator visitor methods to be mixed into TypeChecker.prototype
|
|
15
|
+
*/
|
|
16
|
+
export const operatorVisitors = {
|
|
17
|
+
/**
|
|
18
|
+
* Visit a binary operation
|
|
19
|
+
* Uses static overload resolution to determine result type.
|
|
20
|
+
* Checks user-defined overloads first, then falls back to primitives.
|
|
21
|
+
*/
|
|
22
|
+
visitBinaryOp(node, env) {
|
|
23
|
+
const leftType = this.visit(node.left, env);
|
|
24
|
+
const rightType = this.visit(node.right, env);
|
|
25
|
+
|
|
26
|
+
const op = node.operator;
|
|
27
|
+
|
|
28
|
+
// Short-circuit logical operators don't go through overloads
|
|
29
|
+
if (op === "&&" || op === "||") {
|
|
30
|
+
return this.setType(node, BOOL);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Map operator to function name
|
|
34
|
+
const funcName = BINARY_OP_NAMES[op];
|
|
35
|
+
if (funcName) {
|
|
36
|
+
// Use left operand's location for better error positioning
|
|
37
|
+
// (node.location points to after the expression, which is often wrong)
|
|
38
|
+
const errorLocation = node.left?.location || node.location;
|
|
39
|
+
const argTypes = [leftType, rightType];
|
|
40
|
+
|
|
41
|
+
// Operators only use built-in primitives, not user-defined functions
|
|
42
|
+
// Users cannot overload operators like + by defining 'add' functions
|
|
43
|
+
const outputType = this.resolveOverloadStatic(
|
|
44
|
+
funcName,
|
|
45
|
+
argTypes,
|
|
46
|
+
node,
|
|
47
|
+
errorLocation
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (outputType) {
|
|
51
|
+
// Convert structured type back to class-based type
|
|
52
|
+
const resultType = this.fromStructuredType(outputType);
|
|
53
|
+
return this.setType(node, resultType);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Overload resolution failed - error already added
|
|
57
|
+
// Fall back to a reasonable type
|
|
58
|
+
if (["<", ">", "<=", ">=", "==", "!="].includes(op)) {
|
|
59
|
+
return this.setType(node, BOOL);
|
|
60
|
+
}
|
|
61
|
+
return this.setType(node, freshTypeVar());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Unknown operator
|
|
65
|
+
return this.setType(node, freshTypeVar());
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Visit a unary operation
|
|
70
|
+
* Uses static overload resolution to determine result type.
|
|
71
|
+
* Checks user-defined overloads first, then falls back to primitives.
|
|
72
|
+
*/
|
|
73
|
+
visitUnaryOp(node, env) {
|
|
74
|
+
const operandType = this.visit(node.operand, env);
|
|
75
|
+
|
|
76
|
+
const op = node.operator;
|
|
77
|
+
|
|
78
|
+
// Map operator to function name
|
|
79
|
+
const funcName = UNARY_OP_NAMES[op];
|
|
80
|
+
if (funcName) {
|
|
81
|
+
// Use operand's location for better error positioning
|
|
82
|
+
const errorLocation = node.operand?.location || node.location;
|
|
83
|
+
const argTypes = [operandType];
|
|
84
|
+
|
|
85
|
+
// Operators only use built-in primitives, not user-defined functions
|
|
86
|
+
const outputType = this.resolveOverloadStatic(
|
|
87
|
+
funcName,
|
|
88
|
+
argTypes,
|
|
89
|
+
node,
|
|
90
|
+
errorLocation
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (outputType) {
|
|
94
|
+
const resultType = this.fromStructuredType(outputType);
|
|
95
|
+
return this.setType(node, resultType);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Overload resolution failed - error already added
|
|
99
|
+
// Fall back based on operator
|
|
100
|
+
if (op === "!") {
|
|
101
|
+
return this.setType(node, BOOL);
|
|
102
|
+
}
|
|
103
|
+
return this.setType(node, operandType);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Unknown operator - preserve operand type
|
|
107
|
+
return this.setType(node, operandType);
|
|
108
|
+
},
|
|
109
|
+
};
|