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,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function Visitors - mixin for TypeChecker
|
|
3
|
+
*
|
|
4
|
+
* Visitors for function definitions and function calls.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
FunctionType,
|
|
9
|
+
RecordType,
|
|
10
|
+
ArrayType,
|
|
11
|
+
TypeVariable,
|
|
12
|
+
PromotedType,
|
|
13
|
+
LiteralType,
|
|
14
|
+
LiteralUnionType,
|
|
15
|
+
UNIT,
|
|
16
|
+
freshTypeVar,
|
|
17
|
+
unify,
|
|
18
|
+
formatType,
|
|
19
|
+
fromTypeAnnotation,
|
|
20
|
+
typeEquals,
|
|
21
|
+
isAssignableTo,
|
|
22
|
+
} from "../../types/types.js";
|
|
23
|
+
import { OverloadedFunctionType } from "../OverloadedFunctionType.js";
|
|
24
|
+
import { ExtensionInfo } from "../TypeEnvironment.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Function visitor methods to be mixed into TypeChecker.prototype
|
|
28
|
+
*/
|
|
29
|
+
export const functionVisitors = {
|
|
30
|
+
/**
|
|
31
|
+
* Visit a function definition
|
|
32
|
+
*/
|
|
33
|
+
visitFunctionDefinition(node, env) {
|
|
34
|
+
const funcEnv = env.createChild();
|
|
35
|
+
|
|
36
|
+
// Start tracking deferred constraints for this function
|
|
37
|
+
this.pushConstraintContext();
|
|
38
|
+
|
|
39
|
+
// Process parameters and create a map from parameter name to index
|
|
40
|
+
const paramTypes = [];
|
|
41
|
+
const paramNameToIndex = new Map();
|
|
42
|
+
for (let i = 0; i < node.parameters.length; i++) {
|
|
43
|
+
const param = node.parameters[i];
|
|
44
|
+
let paramType;
|
|
45
|
+
if (param.typeAnnotation) {
|
|
46
|
+
paramType = fromTypeAnnotation(param.typeAnnotation, env);
|
|
47
|
+
|
|
48
|
+
// Validate promoted parameters: look up converter(s) and set sourceType(s)
|
|
49
|
+
if (paramType instanceof PromotedType) {
|
|
50
|
+
// Validate that paramRef matches the parameter name
|
|
51
|
+
if (paramType.paramRef !== param.name) {
|
|
52
|
+
this.addError(
|
|
53
|
+
`Promotion converter references '${paramType.paramRef}' but parameter is '${param.name}'`,
|
|
54
|
+
param.location
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate each converter in the multi-converter list
|
|
59
|
+
const allSourceTypes = [];
|
|
60
|
+
for (const converter of paramType.converters) {
|
|
61
|
+
const converterType = env.lookup(converter.funcName);
|
|
62
|
+
if (!converterType) {
|
|
63
|
+
this.addError(
|
|
64
|
+
`Converter function '${converter.funcName}' not found`,
|
|
65
|
+
param.location
|
|
66
|
+
);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (converterType instanceof FunctionType) {
|
|
71
|
+
// Single function - validate it has exactly one parameter
|
|
72
|
+
if (converterType.paramTypes.length !== 1) {
|
|
73
|
+
this.addError(
|
|
74
|
+
`Converter '${converter.funcName}' must take exactly one parameter`,
|
|
75
|
+
param.location
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
// Set the sourceType from the converter's input type
|
|
79
|
+
converter.sourceType = converterType.paramTypes[0];
|
|
80
|
+
|
|
81
|
+
// Validate that converter returns the target type
|
|
82
|
+
if (!typeEquals(converterType.returnType, paramType.targetType)) {
|
|
83
|
+
this.addError(
|
|
84
|
+
`Converter '${converter.funcName}' returns ${formatType(converterType.returnType)}, expected ${formatType(paramType.targetType)}`,
|
|
85
|
+
param.location
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
allSourceTypes.push({ type: converter.sourceType, funcName: converter.funcName });
|
|
90
|
+
}
|
|
91
|
+
} else if (converterType instanceof OverloadedFunctionType) {
|
|
92
|
+
// Overloaded function - find matching overloads
|
|
93
|
+
const validSourceTypes = [];
|
|
94
|
+
|
|
95
|
+
// Check user-defined overloads
|
|
96
|
+
for (const { funcType } of converterType.userOverloads) {
|
|
97
|
+
if (funcType.paramTypes.length === 1 &&
|
|
98
|
+
typeEquals(funcType.returnType, paramType.targetType)) {
|
|
99
|
+
validSourceTypes.push(funcType.paramTypes[0]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check primitive overloads
|
|
104
|
+
for (const primFn of converterType.overloads) {
|
|
105
|
+
if (primFn.stone && primFn.stone.in.length === 1) {
|
|
106
|
+
const outType = fromTypeAnnotation({ type: 'TypeAnnotation', name: primFn.stone.out }, env);
|
|
107
|
+
if (typeEquals(outType, paramType.targetType)) {
|
|
108
|
+
const inType = fromTypeAnnotation({ type: 'TypeAnnotation', name: primFn.stone.in[0] }, env);
|
|
109
|
+
validSourceTypes.push(inType);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (validSourceTypes.length === 0) {
|
|
115
|
+
this.addError(
|
|
116
|
+
`No overload of '${converter.funcName}' takes one parameter and returns ${formatType(paramType.targetType)}`,
|
|
117
|
+
param.location
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
converter.sourceType = validSourceTypes[0];
|
|
121
|
+
converter.validSourceTypes = validSourceTypes;
|
|
122
|
+
allSourceTypes.push({ type: validSourceTypes[0], funcName: converter.funcName });
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
this.addError(
|
|
126
|
+
`'${converter.funcName}' is not a function`,
|
|
127
|
+
param.location
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check for overlapping source types (error if two converters accept the same type)
|
|
133
|
+
for (let i = 0; i < allSourceTypes.length; i++) {
|
|
134
|
+
for (let j = i + 1; j < allSourceTypes.length; j++) {
|
|
135
|
+
if (allSourceTypes[i].type && allSourceTypes[j].type &&
|
|
136
|
+
typeEquals(allSourceTypes[i].type, allSourceTypes[j].type)) {
|
|
137
|
+
this.addError(
|
|
138
|
+
`Overlapping source types: converters '${allSourceTypes[i].funcName}' and '${allSourceTypes[j].funcName}' both accept ${formatType(allSourceTypes[i].type)}`,
|
|
139
|
+
param.location
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// For backwards compatibility, set the legacy single sourceType
|
|
146
|
+
if (paramType.converters.length > 0 && paramType.converters[0].sourceType) {
|
|
147
|
+
paramType.sourceType = paramType.converters[0].sourceType;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
paramType = freshTypeVar();
|
|
152
|
+
}
|
|
153
|
+
paramTypes.push(paramType);
|
|
154
|
+
|
|
155
|
+
// For promoted types, define the parameter with the target type in the function scope
|
|
156
|
+
let effectiveType = paramType instanceof PromotedType
|
|
157
|
+
? paramType.targetType
|
|
158
|
+
: paramType;
|
|
159
|
+
|
|
160
|
+
// If the effective type is a schema (RecordType with requiredFields), convert to instance type
|
|
161
|
+
// This allows field access in the function body without "required type descriptor" errors
|
|
162
|
+
if (effectiveType instanceof RecordType && effectiveType.requiredFields) {
|
|
163
|
+
effectiveType = new RecordType(
|
|
164
|
+
effectiveType.fields, // Same fields
|
|
165
|
+
false, // Not open
|
|
166
|
+
false, // Not explicit
|
|
167
|
+
null // No required fields - this is an instance, not a schema
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
funcEnv.define(param.name, effectiveType);
|
|
171
|
+
funcEnv.markImmutable(param.name); // Function parameters are immutable
|
|
172
|
+
paramNameToIndex.set(param.name, i);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Infer return type from body
|
|
176
|
+
let returnType;
|
|
177
|
+
if (node.isExpression) {
|
|
178
|
+
// First visit the body to type-check it
|
|
179
|
+
returnType = this.visit(node.body, funcEnv);
|
|
180
|
+
|
|
181
|
+
// For expression bodies that may contain return statements (e.g., block expressions with if/else),
|
|
182
|
+
// also collect and unify all return statements to ensure consistent types
|
|
183
|
+
const returnInfos = this.collectReturnStatements([node.body]);
|
|
184
|
+
if (returnInfos.length > 0) {
|
|
185
|
+
// Unify all return statement types
|
|
186
|
+
const { unifiedType, errors } = this.unifyReturnTypes(returnInfos);
|
|
187
|
+
for (const error of errors) {
|
|
188
|
+
this.addError(error.message, error.location);
|
|
189
|
+
}
|
|
190
|
+
// Use the unified return type instead of the expression type
|
|
191
|
+
returnType = unifiedType instanceof TypeVariable ? unifiedType.resolve() : unifiedType;
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// Block function - look for return statements
|
|
195
|
+
returnType = this.visitFunctionBody(node.body, funcEnv);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Collect deferred constraints from body analysis
|
|
199
|
+
const collectedConstraints = this.popConstraintContext();
|
|
200
|
+
|
|
201
|
+
// Check against declared return type
|
|
202
|
+
if (node.returnType) {
|
|
203
|
+
const declaredReturnType = fromTypeAnnotation(node.returnType, env);
|
|
204
|
+
|
|
205
|
+
// For literal types, use strict assignability check (inferred must be subset of declared)
|
|
206
|
+
// For other types, use unify for compatibility
|
|
207
|
+
const isLiteralReturn = declaredReturnType instanceof LiteralType ||
|
|
208
|
+
declaredReturnType instanceof LiteralUnionType;
|
|
209
|
+
|
|
210
|
+
if (isLiteralReturn) {
|
|
211
|
+
// Strict check: inferred return type must be assignable to declared type
|
|
212
|
+
if (!isAssignableTo(returnType, declaredReturnType)) {
|
|
213
|
+
this.addError(
|
|
214
|
+
`Function '${node.name}' declared to return ${formatType(declaredReturnType)} but inferred ${formatType(returnType)}`,
|
|
215
|
+
node.location
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
// Use unify for regular types
|
|
220
|
+
const unifyResult = unify(declaredReturnType, returnType);
|
|
221
|
+
if (!unifyResult.success) {
|
|
222
|
+
this.addError(
|
|
223
|
+
`Function '${node.name}' declared to return ${formatType(declaredReturnType)} but inferred ${formatType(returnType)}`,
|
|
224
|
+
node.location
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
returnType = declaredReturnType;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Resolve any TypeVariables that were constrained during body analysis
|
|
232
|
+
// This makes function signatures more precise based on actual usage
|
|
233
|
+
const resolvedParamTypes = paramTypes.map((t, i) => {
|
|
234
|
+
if (t instanceof TypeVariable) {
|
|
235
|
+
const resolved = t.resolve();
|
|
236
|
+
return resolved;
|
|
237
|
+
}
|
|
238
|
+
return t;
|
|
239
|
+
});
|
|
240
|
+
const resolvedReturnType = returnType instanceof TypeVariable
|
|
241
|
+
? returnType.resolve()
|
|
242
|
+
: returnType;
|
|
243
|
+
|
|
244
|
+
// Create function type with any collected deferred constraints
|
|
245
|
+
const funcType = new FunctionType(resolvedParamTypes, resolvedReturnType, collectedConstraints);
|
|
246
|
+
|
|
247
|
+
// Support function overloading: accumulate overloads instead of overwriting
|
|
248
|
+
const existing = env.lookup(node.name);
|
|
249
|
+
|
|
250
|
+
// Note: We allow user functions to shadow primitives. The executor will use
|
|
251
|
+
// the pre-resolved overloadId from type checking, falling back to primitives
|
|
252
|
+
// only if no user overload matches. If a user overload was resolved but can't
|
|
253
|
+
// be found at runtime, the executor throws a clear error.
|
|
254
|
+
|
|
255
|
+
// Generate overloadId for this definition using location or a counter as unique identifier
|
|
256
|
+
let locId;
|
|
257
|
+
if (node.location && node.location.start) {
|
|
258
|
+
locId = `${node.location.start.line}_${node.location.start.column}`;
|
|
259
|
+
} else if (node.location && typeof node.location.line === 'number') {
|
|
260
|
+
locId = `${node.location.line}_${node.location.column || 0}`;
|
|
261
|
+
} else {
|
|
262
|
+
// Fallback to a counter
|
|
263
|
+
this._overloadCounter = (this._overloadCounter || 0) + 1;
|
|
264
|
+
locId = `gen_${this._overloadCounter}`;
|
|
265
|
+
}
|
|
266
|
+
const overloadId = `${node.name}_${locId}`;
|
|
267
|
+
node.overloadId = overloadId;
|
|
268
|
+
// Store overloadId on funcType so we can retrieve it if this becomes overloaded
|
|
269
|
+
funcType._overloadId = overloadId;
|
|
270
|
+
funcType._sourceNode = node;
|
|
271
|
+
|
|
272
|
+
if (existing instanceof OverloadedFunctionType) {
|
|
273
|
+
// Add to existing user-defined overload set
|
|
274
|
+
existing.addUserOverload(funcType, node);
|
|
275
|
+
} else if (existing instanceof FunctionType) {
|
|
276
|
+
// Convert to overloaded: previous definition + new one
|
|
277
|
+
const overloaded = new OverloadedFunctionType(node.name, [], []);
|
|
278
|
+
// Use the stored overloadId and node from the first definition
|
|
279
|
+
const firstOverloadId = existing._overloadId || `${node.name}_first`;
|
|
280
|
+
const firstNode = existing._sourceNode || null;
|
|
281
|
+
overloaded.userOverloads.push({
|
|
282
|
+
funcType: existing,
|
|
283
|
+
node: firstNode,
|
|
284
|
+
overloadId: firstOverloadId
|
|
285
|
+
});
|
|
286
|
+
overloaded.addUserOverload(funcType, node);
|
|
287
|
+
env.define(node.name, overloaded);
|
|
288
|
+
} else {
|
|
289
|
+
// First definition - store as regular FunctionType
|
|
290
|
+
env.define(node.name, funcType);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If this is an extension function, also register it in the extension map
|
|
294
|
+
if (node.isExtension && node.selfType) {
|
|
295
|
+
const extensionInfo = new ExtensionInfo(
|
|
296
|
+
node.name,
|
|
297
|
+
node.selfType,
|
|
298
|
+
funcType
|
|
299
|
+
);
|
|
300
|
+
env.defineExtension(node.name, extensionInfo);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return this.setType(node, funcType);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Visit function body to find return type using two-pass approach
|
|
308
|
+
* 1. Collect all return statements with their types
|
|
309
|
+
* 2. Unify all complete types, report mismatches
|
|
310
|
+
* 3. Back-propagate unified type to incomplete expressions (e.g., empty arrays)
|
|
311
|
+
*/
|
|
312
|
+
visitFunctionBody(body, env) {
|
|
313
|
+
// First, visit all statements to type-check the body
|
|
314
|
+
for (const stmt of body) {
|
|
315
|
+
this.visit(stmt, env);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Collect all return statements (they've already been visited above)
|
|
319
|
+
const returnInfos = [];
|
|
320
|
+
const collectReturns = (statements, isGuardPassthrough = false) => {
|
|
321
|
+
for (const stmt of statements) {
|
|
322
|
+
if (stmt.type === "ReturnStatement") {
|
|
323
|
+
const type = stmt.inferredType || (stmt.value ? stmt.value.inferredType : UNIT) || freshTypeVar();
|
|
324
|
+
const isIncomplete = this.isIncompleteType(type);
|
|
325
|
+
returnInfos.push({ node: stmt, type, isIncomplete, isGuardPassthrough });
|
|
326
|
+
} else if (stmt.type === "BranchExpression") {
|
|
327
|
+
// Check for guard passthrough pattern
|
|
328
|
+
// Pattern: if (x.field != "literal") { return x } - early return guard
|
|
329
|
+
let guardPassthroughInFirstBranch = false;
|
|
330
|
+
if (stmt.paths.length >= 1 && stmt.paths[0].condition) {
|
|
331
|
+
const discriminant = this.extractDiscriminantCheck(stmt.paths[0].condition);
|
|
332
|
+
if (discriminant && discriminant.isNegated) {
|
|
333
|
+
const returnVar = this.getSimpleReturnVar(stmt.paths[0].body);
|
|
334
|
+
if (returnVar === discriminant.varName) {
|
|
335
|
+
guardPassthroughInFirstBranch = true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (let pathIdx = 0; pathIdx < stmt.paths.length; pathIdx++) {
|
|
341
|
+
const branchPath = stmt.paths[pathIdx];
|
|
342
|
+
const isGuardPath = guardPassthroughInFirstBranch && pathIdx === 0;
|
|
343
|
+
collectReturns(branchPath.body, isGuardPath);
|
|
344
|
+
}
|
|
345
|
+
} else if (stmt.type === "LoopExpression") {
|
|
346
|
+
collectReturns(stmt.body);
|
|
347
|
+
} else if (stmt.type === "BlockExpression") {
|
|
348
|
+
collectReturns(stmt.statements || []);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
collectReturns(body);
|
|
353
|
+
|
|
354
|
+
// If no return statements, return UNIT
|
|
355
|
+
if (returnInfos.length === 0) {
|
|
356
|
+
return UNIT;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Unify return types
|
|
360
|
+
const { unifiedType, errors } = this.unifyReturnTypes(returnInfos);
|
|
361
|
+
|
|
362
|
+
// Report any errors
|
|
363
|
+
for (const error of errors) {
|
|
364
|
+
this.addError(error.message, error.location);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Back-propagate unified type to incomplete expressions
|
|
368
|
+
this.resolveIncompleteTypes(returnInfos, unifiedType);
|
|
369
|
+
|
|
370
|
+
return unifiedType instanceof TypeVariable ? unifiedType.resolve() : unifiedType;
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Visit a function call
|
|
375
|
+
* Uses static overload resolution for primitive functions
|
|
376
|
+
*/
|
|
377
|
+
visitFunctionCall(node, env) {
|
|
378
|
+
if (node.callee) {
|
|
379
|
+
// Method call - get the method's FunctionType from visiting the callee
|
|
380
|
+
const calleeType = this.visit(node.callee, env);
|
|
381
|
+
const argTypes = (node.args || []).map((arg) => this.visit(arg, env));
|
|
382
|
+
|
|
383
|
+
// If the callee is a FunctionType, unify argument types and return the return type
|
|
384
|
+
if (calleeType instanceof FunctionType) {
|
|
385
|
+
// Special handling for push method on arrays without declared type:
|
|
386
|
+
// Infer and store the element type from the first push argument
|
|
387
|
+
if (node.callee.type === "MemberAccess" &&
|
|
388
|
+
node.callee.property?.name === "push" &&
|
|
389
|
+
node.callee.object?.type === "Identifier") {
|
|
390
|
+
let arrayVarName = node.callee.object.name;
|
|
391
|
+
// For primed variables (arr'), use base name for declared type storage
|
|
392
|
+
const baseName = arrayVarName.endsWith("'") ? arrayVarName.slice(0, -1) : arrayVarName;
|
|
393
|
+
const arrayType = env.lookup(arrayVarName);
|
|
394
|
+
|
|
395
|
+
// If array has unresolved element type and no declared type, infer from push arg
|
|
396
|
+
if (arrayType instanceof ArrayType &&
|
|
397
|
+
arrayType.elementType instanceof TypeVariable &&
|
|
398
|
+
!arrayType.elementType.resolved &&
|
|
399
|
+
!env.lookupDeclared(baseName) &&
|
|
400
|
+
argTypes.length > 0) {
|
|
401
|
+
// The first argument determines the array's element type
|
|
402
|
+
const argType = argTypes[0];
|
|
403
|
+
// Store as the "inferred declared type" for future pushes (use base name)
|
|
404
|
+
const inferredArrayType = new ArrayType(argType, arrayType.rank);
|
|
405
|
+
env.defineDeclared(baseName, inferredArrayType);
|
|
406
|
+
// Also unify the TypeVariable to resolve it
|
|
407
|
+
unify(arrayType.elementType, argType);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Check argument types against parameter types
|
|
412
|
+
for (let i = 0; i < Math.min(argTypes.length, calleeType.paramTypes.length); i++) {
|
|
413
|
+
const unifyResult = unify(calleeType.paramTypes[i], argTypes[i]);
|
|
414
|
+
if (!unifyResult.success) {
|
|
415
|
+
// Only error if both types are fully resolved (no unresolved type variables)
|
|
416
|
+
const paramHasTypeVar = this.containsUnresolvedTypeVar(calleeType.paramTypes[i]);
|
|
417
|
+
const argHasTypeVar = this.containsUnresolvedTypeVar(argTypes[i]);
|
|
418
|
+
if (!paramHasTypeVar && !argHasTypeVar) {
|
|
419
|
+
this.addError(
|
|
420
|
+
`Method argument ${i + 1} type mismatch: expected ${formatType(calleeType.paramTypes[i])}, got ${formatType(argTypes[i])}`,
|
|
421
|
+
node.location
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return this.setType(node, calleeType.returnType);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Unknown callee type - return fresh type
|
|
430
|
+
return this.setType(node, freshTypeVar());
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const funcName = node.funcName;
|
|
434
|
+
const argTypes = (node.args || []).map((arg) => this.visit(arg, env));
|
|
435
|
+
|
|
436
|
+
// Check environment first for user-defined functions
|
|
437
|
+
let funcType = env.lookup(funcName);
|
|
438
|
+
|
|
439
|
+
// Instantiate fresh type variables for each call (polymorphism)
|
|
440
|
+
// This ensures that calls like print("str") and print([1,2]) work independently
|
|
441
|
+
if (funcType instanceof FunctionType) {
|
|
442
|
+
funcType = funcType.instantiate();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Handle user-defined function overloads
|
|
446
|
+
// If the function has multiple user-defined overloads, resolve statically
|
|
447
|
+
if (funcType instanceof OverloadedFunctionType && funcType.userOverloads.length > 0) {
|
|
448
|
+
const resolved = this.resolveUserOverloadStatic(funcName, argTypes, funcType, node);
|
|
449
|
+
if (resolved) {
|
|
450
|
+
return this.setType(node, resolved.returnType);
|
|
451
|
+
}
|
|
452
|
+
// If resolution failed, error was already added - return fresh type
|
|
453
|
+
return this.setType(node, freshTypeVar());
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Check if this function is defined in the scope chain as a FunctionType (not just an overload).
|
|
457
|
+
// User-defined functions shadow builtins.
|
|
458
|
+
const isUserDefinedFunction = funcType instanceof FunctionType && this.isDefinedInScope(env, funcName);
|
|
459
|
+
|
|
460
|
+
// If it's a user-defined FunctionType, use it directly.
|
|
461
|
+
// This allows user-defined functions to shadow builtins.
|
|
462
|
+
if (isUserDefinedFunction) {
|
|
463
|
+
// Track promotions for codegen
|
|
464
|
+
const promotions = [];
|
|
465
|
+
|
|
466
|
+
// Check argument types against function signature
|
|
467
|
+
if (argTypes.length !== funcType.paramTypes.length) {
|
|
468
|
+
this.addError(
|
|
469
|
+
`Function '${funcName}' expects ${funcType.paramTypes.length} arguments, got ${argTypes.length}`,
|
|
470
|
+
node.location
|
|
471
|
+
);
|
|
472
|
+
} else {
|
|
473
|
+
for (let i = 0; i < argTypes.length; i++) {
|
|
474
|
+
const unifyResult = unify(funcType.paramTypes[i], argTypes[i]);
|
|
475
|
+
|
|
476
|
+
// Track promotion info (converter function name or null)
|
|
477
|
+
promotions.push(unifyResult.promoted ? unifyResult.converter : null);
|
|
478
|
+
|
|
479
|
+
if (!unifyResult.success) {
|
|
480
|
+
const paramType = funcType.paramTypes[i];
|
|
481
|
+
const argType = argTypes[i];
|
|
482
|
+
const paramHasTypeVar = this.containsUnresolvedTypeVar(paramType);
|
|
483
|
+
const argHasTypeVar = this.containsUnresolvedTypeVar(argType);
|
|
484
|
+
|
|
485
|
+
// Check if param type is an explicit constraint (from destructuring)
|
|
486
|
+
// Explicit constraints should always report errors, while implicit constraints
|
|
487
|
+
// (from member access inference) may be flow-dependent and shouldn't error
|
|
488
|
+
const resolvedParam = paramType.resolve ? paramType.resolve() : paramType;
|
|
489
|
+
const isExplicitConstraint = resolvedParam instanceof RecordType && resolvedParam.explicit;
|
|
490
|
+
|
|
491
|
+
// "Missing required field" errors from explicit destructuring constraints
|
|
492
|
+
// should always be reported - the user explicitly asked for those fields
|
|
493
|
+
const isMissingRequiredFieldError = unifyResult.error &&
|
|
494
|
+
unifyResult.error.includes('Missing required field') &&
|
|
495
|
+
isExplicitConstraint;
|
|
496
|
+
|
|
497
|
+
// Other structural errors - only report when param type is fully resolved
|
|
498
|
+
// to avoid false positives when type variables could match different types
|
|
499
|
+
const isOtherStructuralError = !paramHasTypeVar && unifyResult.error &&
|
|
500
|
+
!isMissingRequiredFieldError &&
|
|
501
|
+
(unifyResult.error.includes('Missing') ||
|
|
502
|
+
unifyResult.error.includes('field'));
|
|
503
|
+
|
|
504
|
+
const isStructuralError = isMissingRequiredFieldError || isOtherStructuralError;
|
|
505
|
+
|
|
506
|
+
// Report error if:
|
|
507
|
+
// 1. Both types are fully resolved (no type variables), OR
|
|
508
|
+
// 2. Type kinds are incompatible (e.g., function vs primitive) and arg is concrete, OR
|
|
509
|
+
// 3. It's a structural error (missing required fields) AND param is fully resolved
|
|
510
|
+
const kindsIncompatible = paramType.kind !== argType.kind &&
|
|
511
|
+
!(paramType instanceof TypeVariable) &&
|
|
512
|
+
!(argType instanceof TypeVariable);
|
|
513
|
+
const argIsConcrete = !argHasTypeVar;
|
|
514
|
+
|
|
515
|
+
if ((!paramHasTypeVar && !argHasTypeVar) || (kindsIncompatible && argIsConcrete) || isStructuralError) {
|
|
516
|
+
const errorMsg = isStructuralError
|
|
517
|
+
? `Argument ${i + 1}: ${unifyResult.error}`
|
|
518
|
+
: `Argument ${i + 1} type mismatch: expected ${formatType(paramType)}, got ${formatType(argType)}`;
|
|
519
|
+
this.addError(errorMsg, node.location);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Check deferred constraints from function definition
|
|
525
|
+
this.checkDeferredConstraints(funcName, funcType, argTypes, node);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Store promotions on the call node for codegen
|
|
529
|
+
// Use resolvedUserOverload to match the pattern in compiler.js
|
|
530
|
+
if (promotions.some(p => p !== null)) {
|
|
531
|
+
node.resolvedUserOverload = { promotions };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return this.setType(node, funcType.returnType);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Special handling for parse(raw, schema) - returns an instance of the schema type
|
|
538
|
+
// Works for parse(string, schema) and parse(record, schema)
|
|
539
|
+
if (funcName === 'parse' && argTypes.length === 2 && argTypes[1] instanceof RecordType) {
|
|
540
|
+
// parse(raw, schema) returns a record matching the schema type
|
|
541
|
+
// The second argument is the schema record, but we return an "instance" type
|
|
542
|
+
// (same fields, but without requiredFields markers so field access is allowed)
|
|
543
|
+
const schemaType = argTypes[1];
|
|
544
|
+
const instanceType = new RecordType(
|
|
545
|
+
schemaType.fields, // Same fields
|
|
546
|
+
false, // Not open
|
|
547
|
+
false, // Not explicit
|
|
548
|
+
null // No required fields - this is an instance, not a schema
|
|
549
|
+
);
|
|
550
|
+
return this.setType(node, instanceType);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Try overload resolution for primitives or OverloadedFunctionType
|
|
554
|
+
if (this.overloads[funcName] || funcType instanceof OverloadedFunctionType) {
|
|
555
|
+
// Use function's overloads if available, otherwise use global overloads
|
|
556
|
+
const overloadsToUse = funcType instanceof OverloadedFunctionType
|
|
557
|
+
? funcType.overloads
|
|
558
|
+
: this.overloads[funcName];
|
|
559
|
+
|
|
560
|
+
// Temporarily set overloads for resolution
|
|
561
|
+
const originalOverloads = this.overloads[funcName];
|
|
562
|
+
this.overloads[funcName] = overloadsToUse;
|
|
563
|
+
|
|
564
|
+
const outputType = this.resolveOverloadStatic(funcName, argTypes, node);
|
|
565
|
+
|
|
566
|
+
// Restore original overloads
|
|
567
|
+
if (originalOverloads) {
|
|
568
|
+
this.overloads[funcName] = originalOverloads;
|
|
569
|
+
} else if (funcType instanceof OverloadedFunctionType) {
|
|
570
|
+
delete this.overloads[funcName];
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (outputType) {
|
|
574
|
+
const resultType = this.fromStructuredType(outputType);
|
|
575
|
+
return this.setType(node, resultType);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Overload resolution failed - error already added
|
|
579
|
+
return this.setType(node, freshTypeVar());
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// If it's a type variable (e.g., function parameter), constrain it to FunctionType
|
|
583
|
+
// This enables type inference for higher-order functions like ODE solvers
|
|
584
|
+
if (funcType instanceof TypeVariable) {
|
|
585
|
+
const returnTypeVar = freshTypeVar();
|
|
586
|
+
const expectedFuncType = new FunctionType(argTypes, returnTypeVar);
|
|
587
|
+
// Try to unify - this constrains the TypeVariable to be a function type
|
|
588
|
+
unify(funcType, expectedFuncType);
|
|
589
|
+
return this.setType(node, returnTypeVar);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Not found in environment and not an overload
|
|
593
|
+
if (!funcType) {
|
|
594
|
+
return this.setType(node, freshTypeVar());
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Unknown type - not a function
|
|
598
|
+
this.addError(
|
|
599
|
+
`'${funcName}' is not a function`,
|
|
600
|
+
node.location
|
|
601
|
+
);
|
|
602
|
+
return this.setType(node, freshTypeVar());
|
|
603
|
+
},
|
|
604
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visitor Registration - aggregates all visitor modules
|
|
3
|
+
*
|
|
4
|
+
* Registers all visitor methods on the TypeChecker prototype.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { literalVisitors } from './literals.js';
|
|
8
|
+
import { operatorVisitors } from './operators.js';
|
|
9
|
+
import { functionVisitors } from './functions.js';
|
|
10
|
+
import { controlFlowVisitors } from './controlFlow.js';
|
|
11
|
+
import { statementVisitors } from './statements.js';
|
|
12
|
+
import { moduleVisitors } from './modules.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Register all visitor methods on the TypeChecker prototype.
|
|
16
|
+
*
|
|
17
|
+
* @param {Function} TypeChecker - The TypeChecker class constructor
|
|
18
|
+
*/
|
|
19
|
+
export function registerVisitors(TypeChecker) {
|
|
20
|
+
Object.assign(TypeChecker.prototype,
|
|
21
|
+
literalVisitors,
|
|
22
|
+
operatorVisitors,
|
|
23
|
+
functionVisitors,
|
|
24
|
+
controlFlowVisitors,
|
|
25
|
+
statementVisitors,
|
|
26
|
+
moduleVisitors
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Re-export individual visitor modules for testing
|
|
31
|
+
export {
|
|
32
|
+
literalVisitors,
|
|
33
|
+
operatorVisitors,
|
|
34
|
+
functionVisitors,
|
|
35
|
+
controlFlowVisitors,
|
|
36
|
+
statementVisitors,
|
|
37
|
+
moduleVisitors,
|
|
38
|
+
};
|