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,605 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overload Resolution Methods - mixin for TypeChecker
|
|
3
|
+
*
|
|
4
|
+
* Methods for static overload resolution of both primitive (from OVERLOADS)
|
|
5
|
+
* and user-defined function overloads.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
FunctionType,
|
|
10
|
+
ArrayType,
|
|
11
|
+
TypeVariable,
|
|
12
|
+
unify,
|
|
13
|
+
parseTypeString,
|
|
14
|
+
matchType,
|
|
15
|
+
substituteType,
|
|
16
|
+
canPromote,
|
|
17
|
+
formatType,
|
|
18
|
+
normalizeType,
|
|
19
|
+
} from "../types/types.js";
|
|
20
|
+
import { OverloadedFunctionType } from "./OverloadedFunctionType.js";
|
|
21
|
+
import { getOverloads } from "../../backends/js-vm/primitives/overloads.js";
|
|
22
|
+
import { CompiledFunction } from "../../backends/js-vm/bytecode.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Overload resolution methods to be mixed into TypeChecker.prototype
|
|
26
|
+
*/
|
|
27
|
+
export const overloadResolutionMethods = {
|
|
28
|
+
/**
|
|
29
|
+
* Score how well argument types match a function signature
|
|
30
|
+
* Supports bidirectional inference: TypeVariables are unified with expected types.
|
|
31
|
+
*
|
|
32
|
+
* @param {Array<Object>} argTypes - Structured argument types
|
|
33
|
+
* @param {Array<string>} paramTypeStrings - Parameter type strings from signature
|
|
34
|
+
* @returns {{score: number, bindings: Object, promotions: boolean[], unifications: Array}|null}
|
|
35
|
+
*/
|
|
36
|
+
scoreOverload(argTypes, paramTypeStrings) {
|
|
37
|
+
// Arity must match
|
|
38
|
+
if (argTypes.length !== paramTypeStrings.length) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let totalScore = 0;
|
|
43
|
+
let bindings = {};
|
|
44
|
+
const promotions = new Array(argTypes.length).fill(false);
|
|
45
|
+
const unifications = []; // Pending TypeVariable unifications: [{typeVarId, targetType}]
|
|
46
|
+
const deferredConstraints = []; // Deferred rank constraints: [{paramIndex, type, rankVar, requiredRank}]
|
|
47
|
+
|
|
48
|
+
const polymorphicPositions = []; // Track which positions have unresolved TypeVariables
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < argTypes.length; i++) {
|
|
51
|
+
const argType = argTypes[i];
|
|
52
|
+
const paramPattern = parseTypeString(paramTypeStrings[i]);
|
|
53
|
+
|
|
54
|
+
// Bidirectional inference: if argType is an unresolved TypeVariable,
|
|
55
|
+
// DON'T unify immediately - keep it polymorphic and track the position
|
|
56
|
+
if (argType.kind === "typevar" && !argType.resolved) {
|
|
57
|
+
// Track this position for polymorphic result type inference
|
|
58
|
+
polymorphicPositions.push({ index: i, typeVarId: argType.id, argType });
|
|
59
|
+
// TypeVariable matches any type with score 0.5 (prefer concrete types)
|
|
60
|
+
totalScore += 0.5;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Try exact match
|
|
65
|
+
const matchResult = matchType(paramPattern, argType, { ...bindings });
|
|
66
|
+
if (matchResult.success) {
|
|
67
|
+
bindings = matchResult.bindings || bindings;
|
|
68
|
+
// Collect any TypeVariable unifications from function type matching
|
|
69
|
+
if (matchResult.typeVarUnifications) {
|
|
70
|
+
unifications.push(...matchResult.typeVarUnifications);
|
|
71
|
+
}
|
|
72
|
+
// Score 0 for exact match, no promotion needed
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle deferred constraints (variable rank vs concrete rank)
|
|
77
|
+
if (matchResult.deferred) {
|
|
78
|
+
bindings = matchResult.bindings || bindings;
|
|
79
|
+
// Collect deferred constraints for this parameter
|
|
80
|
+
if (matchResult.constraints) {
|
|
81
|
+
for (const constraint of matchResult.constraints) {
|
|
82
|
+
deferredConstraints.push({
|
|
83
|
+
paramIndex: i,
|
|
84
|
+
...constraint
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Collect any TypeVariable unifications from function type matching
|
|
89
|
+
if (matchResult.typeVarUnifications) {
|
|
90
|
+
unifications.push(...matchResult.typeVarUnifications);
|
|
91
|
+
}
|
|
92
|
+
// Small penalty for deferred match (prefer exact matches)
|
|
93
|
+
totalScore += 0.1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Try promotion (num -> complex or user-defined)
|
|
98
|
+
const promoResult = canPromote(argType, paramPattern);
|
|
99
|
+
if (promoResult.can) {
|
|
100
|
+
totalScore += 1;
|
|
101
|
+
promotions[i] = promoResult.converter; // Store converter name instead of boolean
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handle primitive num -> complex promotion (for structured types in primitive signatures)
|
|
106
|
+
if (
|
|
107
|
+
argType.kind === "primitive" &&
|
|
108
|
+
argType.name === "num" &&
|
|
109
|
+
paramPattern.kind === "primitive" &&
|
|
110
|
+
paramPattern.name === "complex"
|
|
111
|
+
) {
|
|
112
|
+
totalScore += 1;
|
|
113
|
+
promotions[i] = "to_complex"; // Store converter name
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle array<num> -> array<complex> promotion (for structured types in primitive signatures)
|
|
118
|
+
if (
|
|
119
|
+
argType.kind === "array" &&
|
|
120
|
+
argType.elem?.kind === "primitive" &&
|
|
121
|
+
argType.elem.name === "num" &&
|
|
122
|
+
paramPattern.kind === "array" &&
|
|
123
|
+
paramPattern.elem?.kind === "primitive" &&
|
|
124
|
+
paramPattern.elem.name === "complex"
|
|
125
|
+
) {
|
|
126
|
+
// Match ranks
|
|
127
|
+
if (typeof paramPattern.rank === "object" && paramPattern.rank.var) {
|
|
128
|
+
const varName = paramPattern.rank.var;
|
|
129
|
+
if (varName in bindings) {
|
|
130
|
+
if (bindings[varName] !== argType.rank) return null;
|
|
131
|
+
} else {
|
|
132
|
+
bindings[varName] = argType.rank;
|
|
133
|
+
}
|
|
134
|
+
} else if (paramPattern.rank !== argType.rank) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
totalScore += 1;
|
|
138
|
+
promotions[i] = "to_complex"; // Store converter name
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// No match possible
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { score: totalScore, bindings, promotions, unifications, deferredConstraints, polymorphicPositions };
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Resolve overload statically using inferred types
|
|
151
|
+
* Stores result on AST node for executor to use
|
|
152
|
+
*
|
|
153
|
+
* Supports bidirectional inference: when argument types are TypeVariables,
|
|
154
|
+
* they get unified with the expected parameter types from the best matching overload.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} name - Function name (e.g., "add", "sin")
|
|
157
|
+
* @param {Array<Object>} argTypes - Class-based types of arguments
|
|
158
|
+
* @param {Object} node - AST node to annotate with resolved overload
|
|
159
|
+
* @param {Object} errorLocation - Optional location to use for error reporting
|
|
160
|
+
* @returns {Object|null} Output type (structured) or null if no match
|
|
161
|
+
*/
|
|
162
|
+
resolveOverloadStatic(name, argTypes, node, errorLocation = null) {
|
|
163
|
+
const candidates = this.overloads[name] || [];
|
|
164
|
+
if (candidates.length === 0) {
|
|
165
|
+
return null; // Not a known overloaded function
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Convert class-based types to structured types
|
|
169
|
+
// This also populates this.typeVarMap for any TypeVariables encountered
|
|
170
|
+
// Then normalize to flatten nested arrays: array<array<num,1>,1> -> array<num,2>
|
|
171
|
+
const structuredArgTypes = argTypes.map(t => normalizeType(this.toStructuredType(t)));
|
|
172
|
+
|
|
173
|
+
let bestMatch = null;
|
|
174
|
+
let bestScore = Infinity;
|
|
175
|
+
let bestPromotions = [];
|
|
176
|
+
let bestBindings = {};
|
|
177
|
+
let bestUnifications = [];
|
|
178
|
+
let bestDeferredConstraints = [];
|
|
179
|
+
let bestPolymorphicPositions = [];
|
|
180
|
+
|
|
181
|
+
for (const fn of candidates) {
|
|
182
|
+
const sig = fn.stone;
|
|
183
|
+
if (!sig) continue;
|
|
184
|
+
|
|
185
|
+
const result = this.scoreOverload(structuredArgTypes, sig.in);
|
|
186
|
+
if (result && result.score < bestScore) {
|
|
187
|
+
bestScore = result.score;
|
|
188
|
+
bestMatch = fn;
|
|
189
|
+
bestPromotions = result.promotions;
|
|
190
|
+
bestBindings = result.bindings;
|
|
191
|
+
bestUnifications = result.unifications || [];
|
|
192
|
+
bestDeferredConstraints = result.deferredConstraints || [];
|
|
193
|
+
bestPolymorphicPositions = result.polymorphicPositions || [];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!bestMatch) {
|
|
198
|
+
// Only report error if there were no TypeVariables involved
|
|
199
|
+
// (TypeVariables might match at runtime with different types)
|
|
200
|
+
const hasTypeVars = structuredArgTypes.some(t => this.hasUnresolvedTypeVars(t));
|
|
201
|
+
if (!hasTypeVars) {
|
|
202
|
+
const argTypeStrs = structuredArgTypes.map(formatType).join(", ");
|
|
203
|
+
const available = candidates
|
|
204
|
+
.filter(f => f.stone)
|
|
205
|
+
.map(f => ` (${f.stone.in.join(", ")}) -> ${f.stone.out}`)
|
|
206
|
+
.join("\n");
|
|
207
|
+
this.addError(
|
|
208
|
+
`No matching overload for ${name}(${argTypeStrs})\nAvailable:\n${available}`,
|
|
209
|
+
errorLocation || node?.location
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Apply TypeVariable unifications from bidirectional inference
|
|
216
|
+
// This constrains TypeVariables to their expected types
|
|
217
|
+
for (const { typeVarId, targetType } of bestUnifications) {
|
|
218
|
+
const typeVar = this.typeVarMap.get(typeVarId);
|
|
219
|
+
if (typeVar && !typeVar.resolved) {
|
|
220
|
+
// Convert structured type to class-based type and unify
|
|
221
|
+
const classBasedTarget = this.fromStructuredType(targetType);
|
|
222
|
+
unify(typeVar, classBasedTarget);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Record deferred constraints for the current function context
|
|
227
|
+
// These will be attached to the function type being defined
|
|
228
|
+
for (const constraint of bestDeferredConstraints) {
|
|
229
|
+
this.recordDeferredConstraint(constraint);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Unify rank TypeVariables with their constrained values
|
|
233
|
+
// This ensures subsequent code in the function body sees the constrained rank
|
|
234
|
+
for (const constraint of bestDeferredConstraints) {
|
|
235
|
+
if (constraint.type === 'rank' && constraint.paramIndex !== undefined) {
|
|
236
|
+
const argType = argTypes[constraint.paramIndex];
|
|
237
|
+
if (argType instanceof ArrayType && argType.rank instanceof TypeVariable) {
|
|
238
|
+
// Directly bind the TypeVariable to the required rank
|
|
239
|
+
const rankVar = argType.rank.resolve();
|
|
240
|
+
if (rankVar instanceof TypeVariable && !rankVar.resolved) {
|
|
241
|
+
rankVar.resolved = true;
|
|
242
|
+
rankVar.resolvedTo = constraint.requiredRank;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check if we should keep the result type polymorphic
|
|
249
|
+
// This happens when:
|
|
250
|
+
// 1. There are TypeVariable arguments (polymorphicPositions)
|
|
251
|
+
// 2. All matching overloads have consistent pattern: input[i] == output
|
|
252
|
+
let keptPolymorphic = false;
|
|
253
|
+
if (bestPolymorphicPositions.length > 0) {
|
|
254
|
+
const polyPattern = this.inferPolymorphicResultPattern(name, candidates, bestPolymorphicPositions, structuredArgTypes);
|
|
255
|
+
if (polyPattern && polyPattern.preservesInputType) {
|
|
256
|
+
// Return the TypeVariable from the source position as the result type
|
|
257
|
+
const sourceArgType = bestPolymorphicPositions.find(
|
|
258
|
+
p => p.index === polyPattern.sourcePosition
|
|
259
|
+
);
|
|
260
|
+
if (sourceArgType) {
|
|
261
|
+
// Store on AST node for executor (will resolve at runtime)
|
|
262
|
+
if (node) {
|
|
263
|
+
node.resolvedOverload = {
|
|
264
|
+
fn: bestMatch,
|
|
265
|
+
promotions: bestPromotions,
|
|
266
|
+
polymorphic: true,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
keptPolymorphic = true;
|
|
270
|
+
// Return the original TypeVariable - keeps function polymorphic
|
|
271
|
+
return sourceArgType.argType;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Pattern check failed - apply unifications for TypeVariables
|
|
276
|
+
// This is the fallback: unify TypeVars with the best match's parameter types
|
|
277
|
+
if (!keptPolymorphic) {
|
|
278
|
+
const sig = bestMatch.stone;
|
|
279
|
+
for (const { index, typeVarId } of bestPolymorphicPositions) {
|
|
280
|
+
const targetType = parseTypeString(sig.in[index]);
|
|
281
|
+
const typeVar = this.typeVarMap.get(typeVarId);
|
|
282
|
+
if (typeVar && !typeVar.resolved) {
|
|
283
|
+
const classBasedTarget = this.fromStructuredType(targetType);
|
|
284
|
+
unify(typeVar, classBasedTarget);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Compute output type with bindings substituted
|
|
291
|
+
const outputType = substituteType(
|
|
292
|
+
parseTypeString(bestMatch.stone.out),
|
|
293
|
+
bestBindings
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Store on AST node for executor
|
|
297
|
+
if (node) {
|
|
298
|
+
// If bestMatch is a mock (not a real function/CompiledFunction), find the real function from dynamic overloads
|
|
299
|
+
// This happens for complex operations where the type checker has mock signatures
|
|
300
|
+
// but the actual functions are registered dynamically when the complex module loads
|
|
301
|
+
let fnToUse = bestMatch;
|
|
302
|
+
const isRealFunction = typeof bestMatch === 'function' || bestMatch instanceof CompiledFunction;
|
|
303
|
+
if (!isRealFunction && bestMatch.stone) {
|
|
304
|
+
// Look for a real function with matching signature in dynamic overloads
|
|
305
|
+
const dynamicOverloads = getOverloads(name);
|
|
306
|
+
const sigKey = JSON.stringify(bestMatch.stone);
|
|
307
|
+
for (const fn of dynamicOverloads) {
|
|
308
|
+
const fnIsReal = typeof fn === 'function' || fn instanceof CompiledFunction;
|
|
309
|
+
if (fnIsReal && fn.stone && JSON.stringify(fn.stone) === sigKey) {
|
|
310
|
+
fnToUse = fn;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
node.resolvedOverload = {
|
|
317
|
+
fn: fnToUse,
|
|
318
|
+
promotions: bestPromotions,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return outputType;
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Infer if all MATCHING overloads have a consistent pattern where input type == output type.
|
|
327
|
+
* This allows keeping TypeVariables polymorphic instead of unifying them prematurely.
|
|
328
|
+
*
|
|
329
|
+
* Only considers overloads that could actually match the argument types (ignoring TypeVar positions).
|
|
330
|
+
*
|
|
331
|
+
* @param {string} name - Function name
|
|
332
|
+
* @param {Array} candidates - Overload candidates
|
|
333
|
+
* @param {Array} polymorphicPositions - Positions with TypeVariable arguments
|
|
334
|
+
* @param {Array} structuredArgTypes - The actual argument types (with TypeVars)
|
|
335
|
+
* @returns {{preservesInputType: boolean, sourcePosition: number}|null}
|
|
336
|
+
*/
|
|
337
|
+
inferPolymorphicResultPattern(name, candidates, polymorphicPositions, structuredArgTypes) {
|
|
338
|
+
if (polymorphicPositions.length === 0) return null;
|
|
339
|
+
|
|
340
|
+
const polyIndices = new Set(polymorphicPositions.map(p => p.index));
|
|
341
|
+
|
|
342
|
+
// Filter candidates to only those that match the non-polymorphic argument types
|
|
343
|
+
const matchingCandidates = candidates.filter(fn => {
|
|
344
|
+
const sig = fn.stone;
|
|
345
|
+
if (!sig || sig.in.length !== structuredArgTypes.length) return false;
|
|
346
|
+
|
|
347
|
+
// Check that non-polymorphic positions match
|
|
348
|
+
for (let i = 0; i < structuredArgTypes.length; i++) {
|
|
349
|
+
if (polyIndices.has(i)) continue; // Skip TypeVariable positions
|
|
350
|
+
|
|
351
|
+
const argType = structuredArgTypes[i];
|
|
352
|
+
const paramPattern = parseTypeString(sig.in[i]);
|
|
353
|
+
|
|
354
|
+
// Simple compatibility check - does the arg type fit the param pattern?
|
|
355
|
+
const matchResult = matchType(paramPattern, argType, {});
|
|
356
|
+
if (!matchResult.success && !matchResult.deferred) {
|
|
357
|
+
// Also check for promotion
|
|
358
|
+
const promoResult = canPromote(argType, paramPattern);
|
|
359
|
+
if (!promoResult.can) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
if (matchingCandidates.length === 0) return null;
|
|
368
|
+
|
|
369
|
+
// Check each polymorphic position to see if it determines the output type
|
|
370
|
+
for (const { index } of polymorphicPositions) {
|
|
371
|
+
let allMatch = true;
|
|
372
|
+
|
|
373
|
+
for (const fn of matchingCandidates) {
|
|
374
|
+
const sig = fn.stone;
|
|
375
|
+
if (!sig || sig.in.length <= index) {
|
|
376
|
+
allMatch = false;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check if input type at this position matches output type
|
|
381
|
+
const inputType = sig.in[index];
|
|
382
|
+
const outputType = sig.out;
|
|
383
|
+
|
|
384
|
+
if (inputType !== outputType) {
|
|
385
|
+
allMatch = false;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (allMatch) {
|
|
391
|
+
return { preservesInputType: true, sourcePosition: index };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return null;
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Resolve user-defined function overloads at compile time.
|
|
400
|
+
* Selects the best matching overload based on argument types and stores
|
|
401
|
+
* the resolved overloadId on the AST node for the executor.
|
|
402
|
+
*
|
|
403
|
+
* @param {string} funcName - Function name
|
|
404
|
+
* @param {Array} argTypes - Array of argument types (class-based)
|
|
405
|
+
* @param {OverloadedFunctionType} overloadedType - The overloaded function type
|
|
406
|
+
* @param {Object} callNode - AST node of the function call
|
|
407
|
+
* @returns {FunctionType|null} The resolved function type, or null if no match
|
|
408
|
+
*/
|
|
409
|
+
resolveUserOverloadStatic(funcName, argTypes, overloadedType, callNode) {
|
|
410
|
+
// Phase 1: Identify concrete vs ambiguous arguments
|
|
411
|
+
const isAmbiguousArg = (argType) => {
|
|
412
|
+
if (argType instanceof TypeVariable) return true;
|
|
413
|
+
if (argType instanceof FunctionType) {
|
|
414
|
+
return argType.paramTypes.some(p => p instanceof TypeVariable && !p.resolved) ||
|
|
415
|
+
(argType.returnType instanceof TypeVariable && !argType.returnType.resolved);
|
|
416
|
+
}
|
|
417
|
+
if (argType instanceof ArrayType) {
|
|
418
|
+
// Check if element type contains unresolved TypeVariables
|
|
419
|
+
return this.containsUnresolvedTypeVar(argType.elementType);
|
|
420
|
+
}
|
|
421
|
+
return false;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Phase 1: Filter overloads using only concrete arguments
|
|
425
|
+
let phase1Candidates = [];
|
|
426
|
+
for (const { funcType, node, overloadId } of overloadedType.userOverloads) {
|
|
427
|
+
if (funcType.paramTypes.length !== argTypes.length) continue;
|
|
428
|
+
|
|
429
|
+
let valid = true;
|
|
430
|
+
for (let i = 0; i < argTypes.length; i++) {
|
|
431
|
+
const paramType = funcType.paramTypes[i];
|
|
432
|
+
const argType = argTypes[i];
|
|
433
|
+
|
|
434
|
+
if (isAmbiguousArg(argType)) continue;
|
|
435
|
+
|
|
436
|
+
if (this.typesEqual(paramType, argType)) {
|
|
437
|
+
// Exact match
|
|
438
|
+
} else if (this.canPromote(argType, paramType).can) {
|
|
439
|
+
// Promotion possible
|
|
440
|
+
} else if (this.typesCompatible(paramType, argType)) {
|
|
441
|
+
// Compatible
|
|
442
|
+
} else {
|
|
443
|
+
valid = false;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (valid) {
|
|
449
|
+
phase1Candidates.push({ funcType, node, overloadId });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Phase 1.5: If multiple candidates, use function type structure to filter
|
|
454
|
+
if (phase1Candidates.length > 1) {
|
|
455
|
+
const funcArgIndices = [];
|
|
456
|
+
for (let i = 0; i < argTypes.length; i++) {
|
|
457
|
+
if (argTypes[i] instanceof FunctionType) funcArgIndices.push(i);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (funcArgIndices.length > 0) {
|
|
461
|
+
const phase15Candidates = [];
|
|
462
|
+
|
|
463
|
+
for (const candidate of phase1Candidates) {
|
|
464
|
+
let compatible = true;
|
|
465
|
+
|
|
466
|
+
for (const funcIdx of funcArgIndices) {
|
|
467
|
+
const expectedFuncType = candidate.funcType.paramTypes[funcIdx];
|
|
468
|
+
const actualFuncType = argTypes[funcIdx];
|
|
469
|
+
|
|
470
|
+
if (expectedFuncType instanceof FunctionType && actualFuncType instanceof FunctionType) {
|
|
471
|
+
if (expectedFuncType.paramTypes.length !== actualFuncType.paramTypes.length) {
|
|
472
|
+
compatible = false;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (let p = 0; p < actualFuncType.paramTypes.length; p++) {
|
|
477
|
+
let actualParamType = actualFuncType.paramTypes[p];
|
|
478
|
+
if (actualParamType instanceof TypeVariable) actualParamType = actualParamType.resolve();
|
|
479
|
+
if (!(actualParamType instanceof TypeVariable)) {
|
|
480
|
+
if (!this.typesCompatibleOrUnifiable(actualParamType, expectedFuncType.paramTypes[p])) {
|
|
481
|
+
compatible = false;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let actualReturnType = actualFuncType.returnType;
|
|
488
|
+
if (actualReturnType instanceof TypeVariable) actualReturnType = actualReturnType.resolve();
|
|
489
|
+
if (!(actualReturnType instanceof TypeVariable)) {
|
|
490
|
+
if (!this.typesCompatibleOrUnifiable(actualReturnType, expectedFuncType.returnType)) {
|
|
491
|
+
compatible = false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (compatible) {
|
|
498
|
+
phase15Candidates.push(candidate);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (phase15Candidates.length === 1) {
|
|
503
|
+
phase1Candidates = phase15Candidates;
|
|
504
|
+
} else if (phase15Candidates.length > 0 && phase15Candidates.length < phase1Candidates.length) {
|
|
505
|
+
phase1Candidates = phase15Candidates;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// If phase 1 narrowed to exactly one overload, use bidirectional inference
|
|
511
|
+
if (phase1Candidates.length === 1) {
|
|
512
|
+
const best = phase1Candidates[0];
|
|
513
|
+
|
|
514
|
+
for (let i = 0; i < argTypes.length; i++) {
|
|
515
|
+
const argType = argTypes[i];
|
|
516
|
+
const paramType = best.funcType.paramTypes[i];
|
|
517
|
+
|
|
518
|
+
if (isAmbiguousArg(argType)) {
|
|
519
|
+
unify(argType, paramType);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
callNode.resolvedUserOverload = {
|
|
524
|
+
funcType: best.funcType,
|
|
525
|
+
overloadId: best.overloadId,
|
|
526
|
+
};
|
|
527
|
+
return best.funcType;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
const candidates = [];
|
|
532
|
+
|
|
533
|
+
for (const { funcType, node, overloadId } of overloadedType.userOverloads) {
|
|
534
|
+
// Skip if arity doesn't match
|
|
535
|
+
if (funcType.paramTypes.length !== argTypes.length) continue;
|
|
536
|
+
|
|
537
|
+
// Score based on type compatibility
|
|
538
|
+
let score = 0;
|
|
539
|
+
let valid = true;
|
|
540
|
+
|
|
541
|
+
for (let i = 0; i < argTypes.length; i++) {
|
|
542
|
+
const paramType = funcType.paramTypes[i];
|
|
543
|
+
const argType = argTypes[i];
|
|
544
|
+
|
|
545
|
+
// Try to check type compatibility
|
|
546
|
+
if (this.typesEqual(paramType, argType)) {
|
|
547
|
+
// Exact match
|
|
548
|
+
score += 0;
|
|
549
|
+
} else if (this.containsUnresolvedTypeVar(paramType) || this.containsUnresolvedTypeVar(argType)) {
|
|
550
|
+
// Type contains unresolved type variable - can unify, slight penalty
|
|
551
|
+
score += 0.5;
|
|
552
|
+
} else if (this.canPromote(argType, paramType).can) {
|
|
553
|
+
// Promotion possible (num -> complex or user-defined)
|
|
554
|
+
score += 1;
|
|
555
|
+
} else if (this.typesCompatible(paramType, argType)) {
|
|
556
|
+
// Compatible but not exact (e.g., array rank matching)
|
|
557
|
+
score += 0.25;
|
|
558
|
+
} else if (this.canUnifyFunctionTypes(paramType, argType)) {
|
|
559
|
+
// Function types that can unify (e.g., (num, array) -> array with (T, T) -> array)
|
|
560
|
+
score += 0.5;
|
|
561
|
+
} else {
|
|
562
|
+
valid = false;
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (valid) {
|
|
568
|
+
candidates.push({ funcType, node, score, overloadId });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (candidates.length === 0) {
|
|
573
|
+
const argTypeStrs = argTypes.map(t => formatType(t)).join(", ");
|
|
574
|
+
const available = overloadedType.userOverloads
|
|
575
|
+
.map(({ funcType }) => ` ${funcType.toString()}`)
|
|
576
|
+
.join("\n");
|
|
577
|
+
this.addError(
|
|
578
|
+
`No matching overload for '${funcName}(${argTypeStrs})'\nAvailable overloads:\n${available}`,
|
|
579
|
+
callNode?.location
|
|
580
|
+
);
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Select best (lowest score)
|
|
585
|
+
candidates.sort((a, b) => a.score - b.score);
|
|
586
|
+
|
|
587
|
+
if (candidates.length > 1 && candidates[0].score === candidates[1].score) {
|
|
588
|
+
this.addError(
|
|
589
|
+
`Ambiguous overload for '${funcName}' - multiple overloads match with equal specificity`,
|
|
590
|
+
callNode?.location
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const best = candidates[0];
|
|
595
|
+
|
|
596
|
+
// Store resolved overload on AST node for executor
|
|
597
|
+
// The overloadId allows executor to look up the specific definition
|
|
598
|
+
callNode.resolvedUserOverload = {
|
|
599
|
+
funcType: best.funcType,
|
|
600
|
+
overloadId: best.overloadId,
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
return best.funcType;
|
|
604
|
+
},
|
|
605
|
+
};
|