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.
Files changed (68) hide show
  1. package/README.md +52 -0
  2. package/StoneEngine.js +879 -0
  3. package/StoneEngineService.js +1727 -0
  4. package/adapters/FileSystemAdapter.js +230 -0
  5. package/adapters/OutputAdapter.js +208 -0
  6. package/adapters/index.js +6 -0
  7. package/cli/CLIOutputAdapter.js +196 -0
  8. package/cli/DaemonClient.js +349 -0
  9. package/cli/JSONOutputAdapter.js +135 -0
  10. package/cli/ReplSession.js +567 -0
  11. package/cli/ViewerServer.js +590 -0
  12. package/cli/commands/check.js +84 -0
  13. package/cli/commands/daemon.js +189 -0
  14. package/cli/commands/kill.js +66 -0
  15. package/cli/commands/package.js +713 -0
  16. package/cli/commands/ps.js +65 -0
  17. package/cli/commands/run.js +537 -0
  18. package/cli/entry.js +169 -0
  19. package/cli/index.js +14 -0
  20. package/cli/stonec.js +358 -0
  21. package/cli/test-compiler.js +181 -0
  22. package/cli/viewer/index.html +495 -0
  23. package/daemon/IPCServer.js +455 -0
  24. package/daemon/ProcessManager.js +327 -0
  25. package/daemon/ProcessRunner.js +307 -0
  26. package/daemon/daemon.js +398 -0
  27. package/daemon/index.js +16 -0
  28. package/frontend/analysis/index.js +5 -0
  29. package/frontend/analysis/livenessAnalyzer.js +568 -0
  30. package/frontend/analysis/treeShaker.js +265 -0
  31. package/frontend/index.js +20 -0
  32. package/frontend/parsing/astBuilder.js +2196 -0
  33. package/frontend/parsing/index.js +7 -0
  34. package/frontend/parsing/sonParser.js +592 -0
  35. package/frontend/parsing/stoneAstTypes.js +703 -0
  36. package/frontend/parsing/terminal-registry.js +435 -0
  37. package/frontend/parsing/tokenizer.js +692 -0
  38. package/frontend/type-checker/OverloadedFunctionType.js +43 -0
  39. package/frontend/type-checker/TypeEnvironment.js +165 -0
  40. package/frontend/type-checker/bidirectionalInference.js +149 -0
  41. package/frontend/type-checker/index.js +10 -0
  42. package/frontend/type-checker/moduleAnalysis.js +248 -0
  43. package/frontend/type-checker/operatorMappings.js +35 -0
  44. package/frontend/type-checker/overloadResolution.js +605 -0
  45. package/frontend/type-checker/typeChecker.js +452 -0
  46. package/frontend/type-checker/typeCompatibility.js +389 -0
  47. package/frontend/type-checker/visitors/controlFlow.js +483 -0
  48. package/frontend/type-checker/visitors/functions.js +604 -0
  49. package/frontend/type-checker/visitors/index.js +38 -0
  50. package/frontend/type-checker/visitors/literals.js +341 -0
  51. package/frontend/type-checker/visitors/modules.js +159 -0
  52. package/frontend/type-checker/visitors/operators.js +109 -0
  53. package/frontend/type-checker/visitors/statements.js +768 -0
  54. package/frontend/types/index.js +5 -0
  55. package/frontend/types/operatorMap.js +134 -0
  56. package/frontend/types/types.js +2046 -0
  57. package/frontend/utils/errorCollector.js +244 -0
  58. package/frontend/utils/index.js +5 -0
  59. package/frontend/utils/moduleResolver.js +479 -0
  60. package/package.json +50 -0
  61. package/packages/browserCache.js +359 -0
  62. package/packages/fetcher.js +236 -0
  63. package/packages/index.js +130 -0
  64. package/packages/lockfile.js +271 -0
  65. package/packages/manifest.js +291 -0
  66. package/packages/packageResolver.js +356 -0
  67. package/packages/resolver.js +310 -0
  68. 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
+ };