yukigo-haskell-parser 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 (63) hide show
  1. package/.mocharc.json +4 -0
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +10 -0
  4. package/dist/index.d.ts +7 -0
  5. package/dist/index.js +54 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/parser/grammar.d.ts +28 -0
  8. package/dist/parser/grammar.js +404 -0
  9. package/dist/parser/grammar.js.map +1 -0
  10. package/dist/parser/layoutPreprocessor.d.ts +1 -0
  11. package/dist/parser/layoutPreprocessor.js +22 -0
  12. package/dist/parser/layoutPreprocessor.js.map +1 -0
  13. package/dist/parser/lexer.d.ts +42 -0
  14. package/dist/parser/lexer.js +75 -0
  15. package/dist/parser/lexer.js.map +1 -0
  16. package/dist/parser/preprocessor.d.ts +28 -0
  17. package/dist/parser/preprocessor.js +453 -0
  18. package/dist/parser/preprocessor.js.map +1 -0
  19. package/dist/prelude.d.ts +1 -0
  20. package/dist/prelude.js +205 -0
  21. package/dist/prelude.js.map +1 -0
  22. package/dist/typechecker/DeclarationCollector.d.ts +15 -0
  23. package/dist/typechecker/DeclarationCollector.js +80 -0
  24. package/dist/typechecker/DeclarationCollector.js.map +1 -0
  25. package/dist/typechecker/TypeBuilder.d.ts +12 -0
  26. package/dist/typechecker/TypeBuilder.js +113 -0
  27. package/dist/typechecker/TypeBuilder.js.map +1 -0
  28. package/dist/typechecker/checker.d.ts +80 -0
  29. package/dist/typechecker/checker.js +269 -0
  30. package/dist/typechecker/checker.js.map +1 -0
  31. package/dist/typechecker/core.d.ts +14 -0
  32. package/dist/typechecker/core.js +142 -0
  33. package/dist/typechecker/core.js.map +1 -0
  34. package/dist/typechecker/inference.d.ts +69 -0
  35. package/dist/typechecker/inference.js +984 -0
  36. package/dist/typechecker/inference.js.map +1 -0
  37. package/dist/utils/helpers.d.ts +2 -0
  38. package/dist/utils/helpers.js +25 -0
  39. package/dist/utils/helpers.js.map +1 -0
  40. package/dist/utils/types.d.ts +6 -0
  41. package/dist/utils/types.js +53 -0
  42. package/dist/utils/types.js.map +1 -0
  43. package/package.json +36 -0
  44. package/src/index.ts +55 -0
  45. package/src/parser/grammar.ne +463 -0
  46. package/src/parser/grammar.ts +512 -0
  47. package/src/parser/layoutPreprocessor.ts +21 -0
  48. package/src/parser/lexer.ts +77 -0
  49. package/src/parser/preprocessor.ne +375 -0
  50. package/src/parser/preprocessor.ts +502 -0
  51. package/src/prelude.ts +206 -0
  52. package/src/typechecker/DeclarationCollector.ts +104 -0
  53. package/src/typechecker/TypeBuilder.ts +148 -0
  54. package/src/typechecker/checker.ts +395 -0
  55. package/src/typechecker/core.ts +162 -0
  56. package/src/typechecker/inference.ts +1327 -0
  57. package/src/utils/helpers.ts +29 -0
  58. package/src/utils/types.ts +56 -0
  59. package/tests/parser.spec.ts +823 -0
  60. package/tests/prelude.spec.ts +22 -0
  61. package/tests/preprocessor.spec.ts +69 -0
  62. package/tests/typechecker.spec.ts +310 -0
  63. package/tsconfig.json +17 -0
@@ -0,0 +1,1327 @@
1
+ import {
2
+ Application,
3
+ ApplicationPattern,
4
+ ArithmeticBinaryOperation,
5
+ ArithmeticUnaryOperation,
6
+ AsPattern,
7
+ AssignOperation,
8
+ ASTNode,
9
+ BitwiseBinaryOperation,
10
+ BitwiseUnaryOperation,
11
+ BooleanPrimitive,
12
+ CharPrimitive,
13
+ ComparisonOperation,
14
+ CompositionExpression,
15
+ ConsExpression,
16
+ ConsPattern,
17
+ ConstructorPattern,
18
+ DataExpression,
19
+ FieldExpression,
20
+ For,
21
+ FunctorPattern,
22
+ Generator,
23
+ If,
24
+ Lambda,
25
+ LetInExpression,
26
+ ListBinaryOperation,
27
+ ListComprehension,
28
+ ListPattern,
29
+ ListPrimitive,
30
+ ListUnaryOperation,
31
+ LiteralPattern,
32
+ LogicalBinaryOperation,
33
+ LogicalUnaryOperation,
34
+ NilPrimitive,
35
+ NumberPrimitive,
36
+ Otherwise,
37
+ Pattern,
38
+ Print,
39
+ Raise,
40
+ RangeExpression,
41
+ Return,
42
+ StringOperation,
43
+ StringPrimitive,
44
+ Switch,
45
+ SymbolPrimitive,
46
+ TupleExpression,
47
+ TuplePattern,
48
+ TypeCast,
49
+ UnifyOperation,
50
+ UnionPattern,
51
+ VariablePattern,
52
+ Visitor,
53
+ WildcardPattern,
54
+ Yield,
55
+ } from "yukigo-ast";
56
+ import {
57
+ Environment,
58
+ getArgumentTypes,
59
+ getArity,
60
+ isFunctionType,
61
+ Result,
62
+ showType,
63
+ Type,
64
+ functionType,
65
+ TypeConstructor,
66
+ TypeScheme,
67
+ isListType,
68
+ isTupleType,
69
+ listType,
70
+ TypeVar,
71
+ booleanType,
72
+ numberType,
73
+ stringType,
74
+ FunctionRegistrarVisitor,
75
+ FunctionCheckerVisitor,
76
+ getReturnType,
77
+ } from "./checker.js";
78
+ import { CoreHM } from "./core.js";
79
+ import { TypeBuilder } from "./TypeBuilder.js";
80
+ import { inspect } from "util";
81
+
82
+ export class PatternVisitor implements Visitor<void> {
83
+ constructor(
84
+ private coreHM: CoreHM,
85
+ private signatureMap: Map<string, TypeScheme>,
86
+ private expectedType: Type,
87
+ private envs: Environment[],
88
+ private inferenceEngine: InferenceEngine
89
+ ) {}
90
+
91
+ visitVariablePattern(node: VariablePattern) {
92
+ const varName = node.name.value.toString();
93
+ if (this.envs[0].has(varName))
94
+ throw new Error(`Duplicate variable name '${varName}'`);
95
+
96
+ this.envs[0].set(varName, {
97
+ type: "TypeScheme",
98
+ quantifiers: [],
99
+ body: this.expectedType,
100
+ constraints: new Map(),
101
+ });
102
+ }
103
+ visitLiteralPattern(node: LiteralPattern) {
104
+ const value = node.name;
105
+
106
+ const inferredLiteral = value.accept(this.inferenceEngine);
107
+ if (inferredLiteral.success === false) throw Error(inferredLiteral.error);
108
+
109
+ let literalType: Type = inferredLiteral.value;
110
+
111
+ const unifyResult = this.coreHM.unify(literalType, this.expectedType);
112
+ if (unifyResult.success === false)
113
+ throw new Error(
114
+ `Literal pattern type mismatch: expected ${showType(
115
+ this.expectedType
116
+ )} but found ${showType(literalType)}`
117
+ );
118
+ }
119
+ visitApplicationPattern(node: ApplicationPattern) {
120
+ const appCtorScheme = this.signatureMap.get(node.symbol.value);
121
+ if (!appCtorScheme)
122
+ throw new Error(`Unknown constructor: ${node.symbol.value}`);
123
+
124
+ const appCtorType = this.coreHM.instantiate(appCtorScheme);
125
+
126
+ // Handle both curried and non-curried constructor types
127
+ let currentType = appCtorType;
128
+ let argIndex = 0;
129
+
130
+ const patternTypes = getArgumentTypes(appCtorType);
131
+ patternTypes.forEach((argType, i) => {
132
+ node.args[argIndex].accept(
133
+ new PatternVisitor(
134
+ this.coreHM,
135
+ this.signatureMap,
136
+ argType,
137
+ this.envs,
138
+ this.inferenceEngine
139
+ )
140
+ );
141
+ });
142
+
143
+ // Check if we've consumed all expected arguments
144
+ const unifyResult = this.coreHM.unify(currentType, this.expectedType);
145
+ if (unifyResult.success === false)
146
+ throw new Error(
147
+ `Constructor ${node.symbol.value} arguments don't match expected type: ${unifyResult.error}`
148
+ );
149
+ }
150
+ visitTuplePattern(node: TuplePattern) {
151
+ let tupleElementTypes: Type[];
152
+
153
+ if (this.expectedType.type === "TypeVar") {
154
+ tupleElementTypes = node.elements.map(() => this.coreHM.freshVar());
155
+ const tupleType: Type = {
156
+ type: "TypeConstructor",
157
+ name: "Tuple",
158
+ args: tupleElementTypes,
159
+ };
160
+
161
+ const unifyResult = this.coreHM.unify(this.expectedType, tupleType);
162
+ if (unifyResult.success === false)
163
+ throw new Error(`Pattern type mismatch: ${unifyResult.error}`);
164
+ } else if (isTupleType(this.expectedType)) {
165
+ if (node.elements.length !== this.expectedType.args.length)
166
+ throw new Error(`Tuple arity mismatch`);
167
+ tupleElementTypes = this.expectedType.args;
168
+ } else {
169
+ throw new Error(
170
+ `Pattern expects a tuple but found non-tuple type: ${showType(
171
+ this.expectedType
172
+ )}`
173
+ );
174
+ }
175
+
176
+ node.elements.forEach((pattern, i) => {
177
+ new PatternVisitor(
178
+ this.coreHM,
179
+ this.signatureMap,
180
+ tupleElementTypes[i],
181
+ this.envs,
182
+ this.inferenceEngine
183
+ ).visit(pattern);
184
+ });
185
+ }
186
+ visitListPattern(node: ListPattern) {
187
+ let elemType: Type;
188
+
189
+ if (this.expectedType.type === "TypeVar") {
190
+ elemType = this.coreHM.freshVar();
191
+ const listT = listType(elemType);
192
+ const unifyResult = this.coreHM.unify(this.expectedType, listT);
193
+ if (unifyResult.success === false)
194
+ throw new Error(`Pattern type mismatch: ${unifyResult.error}`);
195
+ } else if (isListType(this.expectedType)) {
196
+ elemType = this.expectedType.args[0];
197
+ } else {
198
+ throw new Error(`Pattern expects a list but found non-list type`);
199
+ }
200
+
201
+ node.elements.forEach((pat) => {
202
+ new PatternVisitor(
203
+ this.coreHM,
204
+ this.signatureMap,
205
+ elemType,
206
+ this.envs,
207
+ this.inferenceEngine
208
+ ).visit(pat);
209
+ });
210
+ }
211
+ visitFunctorPattern(node: FunctorPattern): Result<Type> {
212
+ throw new Error("Method not implemented.");
213
+ }
214
+ visitAsPattern(node: AsPattern) {
215
+ // Process the main pattern
216
+ new PatternVisitor(
217
+ this.coreHM,
218
+ this.signatureMap,
219
+ this.expectedType,
220
+ this.envs,
221
+ this.inferenceEngine
222
+ ).visit(node.pattern);
223
+
224
+ // Process the alias if it's a variable
225
+ if (node.alias instanceof VariablePattern) {
226
+ const varName = node.alias.name.value.toString();
227
+ if (this.envs[0].has(varName))
228
+ throw new Error(`Duplicate variable name: ${varName}`);
229
+
230
+ this.envs[0].set(varName, {
231
+ type: "TypeScheme",
232
+ quantifiers: [],
233
+ body: this.expectedType,
234
+ constraints: new Map(),
235
+ });
236
+ }
237
+ }
238
+ visitWildcardPattern(node: WildcardPattern): Result<Type> {
239
+ return {
240
+ success: true,
241
+ value: this.coreHM.freshVar(),
242
+ };
243
+ }
244
+ visitUnionPattern(node: UnionPattern): Result<Type> {
245
+ throw new Error("Method not implemented.");
246
+ }
247
+ visitConstructorPattern(node: ConstructorPattern) {
248
+ const ctorScheme = this.signatureMap.get(node.constr);
249
+ if (!ctorScheme) throw new Error(`Unknown constructor: ${node.constr}`);
250
+
251
+ const ctorType = this.coreHM.instantiate(ctorScheme);
252
+ // Extract argument types and return type from constructor
253
+ const argTypes = getArgumentTypes(ctorType);
254
+ const returnType = getReturnType(ctorType);
255
+
256
+ if (argTypes.length !== node.patterns.length)
257
+ throw new Error(
258
+ `Constructor ${node.constr} expects ${argTypes.length} arguments but pattern has ${node.patterns.length}`
259
+ );
260
+
261
+ // Unify the constructor's return type with expected pattern type
262
+ const unifyResult = this.coreHM.unify(returnType, this.expectedType);
263
+ if (!unifyResult.success)
264
+ throw new Error(
265
+ `Constructor ${node.constr} return type ${showType(
266
+ returnType
267
+ )} doesn't match expected type ${showType(this.expectedType)}`
268
+ );
269
+
270
+ // Apply substitutions to argument types
271
+ const subst = unifyResult.value;
272
+ const unifiedArgTypes = argTypes.map((argType) =>
273
+ this.coreHM.applySubst(subst, argType)
274
+ );
275
+
276
+ // Process each argument pattern with its correct type
277
+ node.patterns.forEach((pattern, i) => {
278
+ new PatternVisitor(
279
+ this.coreHM,
280
+ this.signatureMap,
281
+ unifiedArgTypes[i],
282
+ this.envs,
283
+ this.inferenceEngine
284
+ ).visit(pattern);
285
+ });
286
+ }
287
+ visitConsPattern(node: ConsPattern) {
288
+ const elemType = this.coreHM.freshVar();
289
+ const listT = listType(elemType);
290
+
291
+ // Unify the expected type with the list type
292
+ const unifyResult = this.coreHM.unify(this.expectedType, listT);
293
+ if (unifyResult.success === false)
294
+ throw new Error(`Pattern type mismatch: ${unifyResult.error}`);
295
+
296
+ // After unification, get the element type from the unified type
297
+ const unifiedElemType = this.coreHM.applySubst(unifyResult.value, elemType);
298
+
299
+ // Process head pattern with the element type
300
+ new PatternVisitor(
301
+ this.coreHM,
302
+ this.signatureMap,
303
+ unifiedElemType,
304
+ this.envs,
305
+ this.inferenceEngine
306
+ ).visit(node.head);
307
+ // Process tail pattern with a list of the same element type
308
+ const tailType = listType(unifiedElemType);
309
+ new PatternVisitor(
310
+ this.coreHM,
311
+ this.signatureMap,
312
+ tailType,
313
+ this.envs,
314
+ this.inferenceEngine
315
+ ).visit(node.tail);
316
+ }
317
+ visit(node: Pattern): void {
318
+ node.accept(this);
319
+ }
320
+ }
321
+
322
+ export class InferenceEngine implements Visitor<Result<Type>> {
323
+ constructor(
324
+ private signatureMap: Map<string, TypeScheme>,
325
+ private coreHM: CoreHM,
326
+ private envs: Environment[]
327
+ ) {}
328
+ visitNumberPrimitive(node: NumberPrimitive): Result<Type> {
329
+ return {
330
+ success: true,
331
+ value: numberType,
332
+ };
333
+ }
334
+ visitBooleanPrimitive(node: BooleanPrimitive): Result<Type> {
335
+ return {
336
+ success: true,
337
+ value: booleanType,
338
+ };
339
+ }
340
+ visitStringPrimitive(node: StringPrimitive): Result<Type> {
341
+ return {
342
+ success: true,
343
+ value: stringType,
344
+ };
345
+ }
346
+ visitListPrimitive(node: ListPrimitive): Result<Type> {
347
+ if (node.elements.length === 0) {
348
+ // Empty list - polymorphic
349
+ const elemType = this.coreHM.freshVar();
350
+ return {
351
+ success: true,
352
+ value: listType(elemType),
353
+ };
354
+ }
355
+
356
+ // Infer type of first element
357
+ const firstResult = node.elements[0].accept(this);
358
+ if (firstResult.success === false) return firstResult;
359
+
360
+ // Check all elements match first element's type
361
+ for (const element of node.elements.slice(1)) {
362
+ const elemResult = element.accept(this);
363
+ if (elemResult.success === false) return elemResult;
364
+ const unifyResult = this.coreHM.unify(
365
+ elemResult.value,
366
+ firstResult.value
367
+ );
368
+ if (unifyResult.success === false) {
369
+ return {
370
+ success: false,
371
+ error: `List elements must have the same type`,
372
+ };
373
+ }
374
+ }
375
+
376
+ return {
377
+ success: true,
378
+ value: listType(firstResult.value),
379
+ };
380
+ }
381
+ visitNilPrimitive(node: NilPrimitive): Result<Type> {
382
+ return {
383
+ success: true,
384
+ value: {
385
+ type: "TypeConstructor",
386
+ name: "YuNil",
387
+ args: [],
388
+ },
389
+ };
390
+ }
391
+ visitCharPrimitive(node: CharPrimitive): Result<Type> {
392
+ return {
393
+ success: true,
394
+ value: {
395
+ type: "TypeConstructor",
396
+ name: "YuChar",
397
+ args: [],
398
+ },
399
+ };
400
+ }
401
+ visitSymbolPrimitive(node: SymbolPrimitive): Result<Type> {
402
+ const name = node.value;
403
+ const searchRes = searchInEnvironments(this.envs, name);
404
+ if (searchRes.success === false) return searchRes;
405
+ return { success: true, value: this.coreHM.instantiate(searchRes.value) };
406
+ }
407
+ visitArithmeticUnaryOperation(node: ArithmeticUnaryOperation): Result<Type> {
408
+ const operandResult = node.operand.accept(this);
409
+ if (!operandResult.success) return operandResult;
410
+
411
+ const unifyOperand = this.coreHM.unify(operandResult.value, numberType);
412
+ if (!unifyOperand.success)
413
+ return {
414
+ success: false,
415
+ error: `Operand of ${node.operator} must be a number`,
416
+ };
417
+
418
+ return { success: true, value: numberType };
419
+ }
420
+ visitArithmeticBinaryOperation(
421
+ node: ArithmeticBinaryOperation
422
+ ): Result<Type> {
423
+ const leftResult = node.left.accept(this);
424
+ if (!leftResult.success) return leftResult;
425
+
426
+ const rightResult = node.right.accept(this);
427
+ if (!rightResult.success) return rightResult;
428
+
429
+ const unifyLeft = this.coreHM.unify(leftResult.value, numberType);
430
+ if (!unifyLeft.success)
431
+ return {
432
+ success: false,
433
+ error: `Left operand of ${node.operator} must be a number`,
434
+ };
435
+ const unifyRight = this.coreHM.unify(rightResult.value, numberType);
436
+ if (!unifyRight.success)
437
+ return {
438
+ success: false,
439
+ error: `Right operand of ${node.operator} must be a number`,
440
+ };
441
+
442
+ return { success: true, value: numberType };
443
+ }
444
+ visitListUnaryOperation(node: ListUnaryOperation): Result<Type> {
445
+ switch (node.operator) {
446
+ case "DetectMin":
447
+ case "DetectMax": {
448
+ const operandResult = node.operand.accept(this);
449
+
450
+ if (!operandResult.success) return operandResult;
451
+
452
+ // Operand must be a list of ordenables (YuNumber, YuString, YuChar)
453
+ const ordType: TypeVar = this.coreHM.freshVar(["Ord"]);
454
+ const listT: TypeConstructor = listType(ordType);
455
+ const unifyOperand = this.coreHM.unify(operandResult.value, listT);
456
+ if (!unifyOperand.success)
457
+ return {
458
+ success: false,
459
+ error: `${node.operator} expects operand to be an Ordenable Type`,
460
+ };
461
+ const finalType = this.coreHM.applySubst(unifyOperand.value, ordType);
462
+ // Result is a the resolved type of the elements of the list
463
+ return { success: true, value: finalType };
464
+ }
465
+ case "Size": {
466
+ const operandResult = node.operand.accept(this);
467
+
468
+ if (!operandResult.success) return operandResult;
469
+ // Operand must be a YuList
470
+ const freshInputVar = this.coreHM.freshVar();
471
+ const listInputType: TypeConstructor = listType(freshInputVar);
472
+ const unifyOperand = this.coreHM.unify(
473
+ operandResult.value,
474
+ listInputType
475
+ );
476
+
477
+ if (!unifyOperand.success)
478
+ return {
479
+ success: false,
480
+ error: `${node.operator} expects operand must be a YuList`,
481
+ };
482
+
483
+ // Result is a YuNumber
484
+ return { success: true, value: numberType };
485
+ }
486
+
487
+ default:
488
+ return {
489
+ success: false,
490
+ error: `Unknown Unary List operation with operator ${node.operator}.`,
491
+ };
492
+ }
493
+ }
494
+ visitListBinaryOperation(node: ListBinaryOperation): Result<Type> {
495
+ switch (node.operator) {
496
+ case "Collect": {
497
+ const leftResult = node.left.accept(this);
498
+ if (!leftResult.success) return leftResult;
499
+
500
+ const rightResult = node.right.accept(this);
501
+ if (!rightResult.success) return rightResult;
502
+
503
+ const funcArity = getArity(leftResult.value);
504
+ if (funcArity !== 1)
505
+ return {
506
+ success: false,
507
+ error: `${node.operator}'s left operand expects to have only one argument`,
508
+ };
509
+
510
+ // Right-hand side must be a list [a]
511
+ const freshInputVar = this.coreHM.freshVar();
512
+ const listInputType: TypeConstructor = listType(freshInputVar);
513
+
514
+ const unifyRight = this.coreHM.unify(rightResult.value, listInputType);
515
+ if (!unifyRight.success)
516
+ return {
517
+ success: false,
518
+ error: `${node.operator}'s right operand must be a list`,
519
+ };
520
+
521
+ const elementType = unifyRight.value.get(freshInputVar.id);
522
+
523
+ // Left-hand side must be a function: elementType -> outputType
524
+ const freshOutputVar = this.coreHM.freshVar();
525
+ const funcType: TypeConstructor = functionType(
526
+ elementType,
527
+ freshOutputVar
528
+ );
529
+
530
+ const unifyLeft = this.coreHM.unify(leftResult.value, funcType);
531
+ if (!unifyLeft.success) {
532
+ return {
533
+ success: false,
534
+ error: `${
535
+ node.operator
536
+ }'s left operand must be a function of type ${showType(
537
+ elementType
538
+ )} -> a`,
539
+ };
540
+ }
541
+
542
+ // Result is a list of the function's output type: [outputType]
543
+
544
+ const resultType: TypeConstructor = listType(freshOutputVar);
545
+
546
+ const subType = this.coreHM.applySubst(unifyLeft.value, resultType);
547
+
548
+ // Return the result type, now fully resolved with substitutions
549
+ return { success: true, value: subType };
550
+ }
551
+ case "Select": {
552
+ const leftResult = node.left.accept(this);
553
+ if (!leftResult.success) return leftResult;
554
+
555
+ const rightResult = node.right.accept(this);
556
+ if (!rightResult.success) return rightResult;
557
+
558
+ const funcArity = getArity(leftResult.value);
559
+ if (funcArity !== 1)
560
+ return {
561
+ success: false,
562
+ error: `${node.operator}'s left operand expects to have only one argument`,
563
+ };
564
+
565
+ // Right-hand side must be a list [a]
566
+ const freshInputVar = this.coreHM.freshVar();
567
+ const listInputType = listType(freshInputVar);
568
+
569
+ const unifyRight = this.coreHM.unify(rightResult.value, listInputType);
570
+
571
+ if (!unifyRight.success)
572
+ return {
573
+ success: false,
574
+ error: `${node.operator}'s right operand must be a list`,
575
+ };
576
+
577
+ const elementType = unifyRight.value.get(freshInputVar.id);
578
+
579
+ // Left-hand side must be a function: elementType -> Bool
580
+ const funcType: TypeConstructor = functionType(
581
+ elementType,
582
+ booleanType
583
+ );
584
+ const unifyLeft = this.coreHM.unify(leftResult.value, funcType);
585
+ if (!unifyLeft.success) {
586
+ return {
587
+ success: false,
588
+ error: `${
589
+ node.operator
590
+ }'s left operand must be a function of type ${showType(funcType)}`,
591
+ };
592
+ }
593
+
594
+ // Result is a list of the function's output type: [outputType]
595
+
596
+ const resultType = listType(elementType);
597
+
598
+ const subType = this.coreHM.applySubst(unifyLeft.value, resultType);
599
+
600
+ // Return the result type, now fully resolved with substitutions
601
+ return { success: true, value: subType };
602
+ }
603
+ case "Detect": {
604
+ const leftResult = node.left.accept(this);
605
+ if (!leftResult.success) return leftResult;
606
+
607
+ const rightResult = node.right.accept(this);
608
+ if (!rightResult.success) return rightResult;
609
+
610
+ const funcArity = getArity(leftResult.value);
611
+ if (funcArity !== 1)
612
+ return {
613
+ success: false,
614
+ error: `${node.operator}'s left operand expects to have only one argument`,
615
+ };
616
+
617
+ // Right-hand side must be a list [a]
618
+ const freshInputVar = this.coreHM.freshVar();
619
+ const listInputType = listType(freshInputVar);
620
+
621
+ const unifyRight = this.coreHM.unify(rightResult.value, listInputType);
622
+
623
+ if (!unifyRight.success)
624
+ return {
625
+ success: false,
626
+ error: `${node.operator}'s right operand must be a list`,
627
+ };
628
+
629
+ const elementType = unifyRight.value.get(freshInputVar.id);
630
+
631
+ // Left-hand side must be a function: elementType -> Bool
632
+ const funcType: TypeConstructor = functionType(
633
+ elementType,
634
+ booleanType
635
+ );
636
+ const unifyLeft = this.coreHM.unify(leftResult.value, funcType);
637
+ if (!unifyLeft.success) {
638
+ return {
639
+ success: false,
640
+ error: `${
641
+ node.operator
642
+ }'s left operand must be a function of type ${showType(funcType)}`,
643
+ };
644
+ }
645
+
646
+ // Result is a list of the function's output type: [outputType]
647
+ const subType = this.coreHM.applySubst(unifyLeft.value, elementType);
648
+
649
+ // Return the result type, now fully resolved with substitutions
650
+ return { success: true, value: subType };
651
+ }
652
+ case "AnySatisfy":
653
+ case "AllSatisfy": {
654
+ const leftResult = node.left.accept(this);
655
+ if (!leftResult.success) return leftResult;
656
+
657
+ const rightResult = node.right.accept(this);
658
+ if (!rightResult.success) return rightResult;
659
+
660
+ const funcArity = getArity(leftResult.value);
661
+ if (funcArity !== 1)
662
+ return {
663
+ success: false,
664
+ error: `${node.operator}'s left operand expects to have only one argument`,
665
+ };
666
+
667
+ // Right-hand side must be a list [a]
668
+ const freshInputVar = this.coreHM.freshVar();
669
+ const listInputType: TypeConstructor = listType(freshInputVar);
670
+
671
+ const unifyRight = this.coreHM.unify(rightResult.value, listInputType);
672
+
673
+ if (!unifyRight.success)
674
+ return {
675
+ success: false,
676
+ error: `${node.operator}'s right operand must be a list`,
677
+ };
678
+
679
+ const elementType = unifyRight.value.get(freshInputVar.id);
680
+
681
+ // Left-hand side must be a function: elementType -> Bool
682
+ const funcType: TypeConstructor = functionType(
683
+ elementType,
684
+ booleanType
685
+ );
686
+ const unifyLeft = this.coreHM.unify(leftResult.value, funcType);
687
+ if (!unifyLeft.success) {
688
+ return {
689
+ success: false,
690
+ error: `${
691
+ node.operator
692
+ }'s left operand must be a function of type ${showType(funcType)}`,
693
+ };
694
+ }
695
+
696
+ // Result is a list of the function's output type: [outputType]
697
+
698
+ const subType = this.coreHM.applySubst(unifyLeft.value, booleanType);
699
+
700
+ // Return the result type, now fully resolved with substitutions
701
+ return { success: true, value: subType };
702
+ }
703
+ case "Concat": {
704
+ const leftResult = node.left.accept(this);
705
+ if (!leftResult.success) return leftResult;
706
+
707
+ const rightResult = node.right.accept(this);
708
+ if (!rightResult.success) return rightResult;
709
+
710
+ // Create a fresh type variable for the element type
711
+ const elemType = this.coreHM.freshVar();
712
+ const expectedListType = listType(elemType);
713
+
714
+ // First, unify left operand with list type
715
+ const unifyLeft = this.coreHM.unify(leftResult.value, expectedListType);
716
+ if (!unifyLeft.success) {
717
+ return {
718
+ success: false,
719
+ error: `Left operand of concat must be a list, got ${showType(
720
+ leftResult.value
721
+ )}`,
722
+ };
723
+ }
724
+
725
+ // Apply the substitution from left unification to both the element type
726
+ // and the right operand type
727
+ const substElemType = this.coreHM.applySubst(unifyLeft.value, elemType);
728
+ const substRightType = this.coreHM.applySubst(
729
+ unifyLeft.value,
730
+ rightResult.value
731
+ );
732
+ const expectedRightType = listType(substElemType);
733
+
734
+ // Now unify the right operand with the updated expected type
735
+ const unifyRight = this.coreHM.unify(substRightType, expectedRightType);
736
+ if (!unifyRight.success) {
737
+ return {
738
+ success: false,
739
+ error: `Concat operation requires both operands to be lists of the same type.`,
740
+ };
741
+ }
742
+
743
+ // Combine substitutions and apply to get final result type
744
+ const combinedSubst = this.coreHM.composeSubst(
745
+ unifyRight.value,
746
+ unifyLeft.value
747
+ );
748
+ const finalType = this.coreHM.applySubst(
749
+ combinedSubst,
750
+ listType(elemType)
751
+ );
752
+
753
+ return { success: true, value: finalType };
754
+ }
755
+ case "GetAt": {
756
+ const leftResult = node.left.accept(this);
757
+ if (!leftResult.success) return leftResult;
758
+
759
+ const rightResult = node.right.accept(this);
760
+ if (!rightResult.success) return rightResult;
761
+
762
+ // Create a fresh type variable for the element type
763
+ const elemType = this.coreHM.freshVar();
764
+ const expectedListType = listType(elemType);
765
+
766
+ // First, unify left operand with list type
767
+ const unifyLeft = this.coreHM.unify(leftResult.value, expectedListType);
768
+ if (!unifyLeft.success) {
769
+ return {
770
+ success: false,
771
+ error: `Left operand of concat must be a list, got ${showType(
772
+ leftResult.value
773
+ )}`,
774
+ };
775
+ }
776
+
777
+ // Left-hand side must be a number: YuNumber
778
+ const unifyRight = this.coreHM.unify(rightResult.value, numberType);
779
+ if (!unifyRight.success) {
780
+ return {
781
+ success: false,
782
+ error: `${node.operator}'s left operand must be a ${showType(
783
+ numberType
784
+ )}`,
785
+ };
786
+ }
787
+ const substElemType = this.coreHM.applySubst(unifyLeft.value, elemType);
788
+
789
+ return { success: true, value: substElemType };
790
+ }
791
+ default:
792
+ return {
793
+ success: false,
794
+ error: `Unknown Binary List operation with operator ${node.operator}.`,
795
+ };
796
+ }
797
+ }
798
+ visitComparisonOperation(node: ComparisonOperation): Result<Type> {
799
+ const leftResult = node.left.accept(this);
800
+ if (!leftResult.success) return leftResult;
801
+
802
+ const rightResult = node.right.accept(this);
803
+ if (!rightResult.success) return rightResult;
804
+
805
+ const unifyResult = this.coreHM.unify(leftResult.value, rightResult.value);
806
+ if (!unifyResult.success) {
807
+ return {
808
+ success: false,
809
+ error: `Comparison operands must have the same type`,
810
+ };
811
+ }
812
+
813
+ return {
814
+ success: true,
815
+ value: booleanType,
816
+ };
817
+ }
818
+ visitLogicalBinaryOperation(node: LogicalBinaryOperation): Result<Type> {
819
+ const operator = node.operator;
820
+
821
+ const leftResult = node.left.accept(this);
822
+ if (!leftResult.success) return leftResult;
823
+ const rightResult = node.right.accept(this);
824
+ if (!rightResult.success) return rightResult;
825
+
826
+ const leftSub = this.coreHM.unify(leftResult.value, booleanType);
827
+ if (!leftSub.success)
828
+ return {
829
+ success: false,
830
+ error: `Left side of ${operator} must be a boolean`,
831
+ };
832
+ const rightSub = this.coreHM.unify(rightResult.value, booleanType);
833
+ if (!rightSub.success)
834
+ return {
835
+ success: false,
836
+ error: `Right side of ${operator} must be a boolean`,
837
+ };
838
+
839
+ return { success: true, value: booleanType };
840
+ }
841
+ visitLogicalUnaryOperation(node: LogicalUnaryOperation): Result<Type> {
842
+ throw new Error("Method not implemented.");
843
+ }
844
+ visitBitwiseBinaryOperation(node: BitwiseBinaryOperation): Result<Type> {
845
+ throw new Error("Method not implemented.");
846
+ }
847
+ visitBitwiseUnaryOperation(node: BitwiseUnaryOperation): Result<Type> {
848
+ throw new Error("Method not implemented.");
849
+ }
850
+ visitStringOperation(node: StringOperation): Result<Type> {
851
+ const leftResult = node.left.accept(this);
852
+ if (!leftResult.success) return leftResult;
853
+
854
+ const rightResult = node.right.accept(this);
855
+ if (!rightResult.success) return rightResult;
856
+
857
+ const unifyLeft = this.coreHM.unify(leftResult.value, stringType);
858
+ const unifyRight = this.coreHM.unify(rightResult.value, stringType);
859
+ if (!unifyLeft.success || !unifyRight.success)
860
+ return {
861
+ success: false,
862
+ error: `String operation requires string operands`,
863
+ };
864
+
865
+ return { success: true, value: stringType };
866
+ }
867
+ visitUnifyOperation(node: UnifyOperation): Result<Type> {
868
+ throw new Error("Method not implemented.");
869
+ }
870
+ visitAssignOperation(node: AssignOperation): Result<Type> {
871
+ throw new Error("Method not implemented.");
872
+ }
873
+ visitTupleExpr(node: TupleExpression): Result<Type> {
874
+ const elementResults = node.elements.map((e) => e.accept(this));
875
+ const errors = elementResults.filter((r) => !r.success);
876
+ if (elementResults.every((res) => res.success === true)) {
877
+ const elementTypes = elementResults.map((r) => r.value);
878
+ const tupleType: TypeConstructor = {
879
+ type: "TypeConstructor",
880
+ name: `Tuple`,
881
+ args: elementTypes,
882
+ };
883
+
884
+ return { success: true, value: tupleType };
885
+ } else {
886
+ return errors[0] as Result<Type>;
887
+ }
888
+ }
889
+ visitFieldExpr(node: FieldExpression): Result<Type> {
890
+ throw new Error("Method not implemented.");
891
+ }
892
+ visitDataExpr(node: DataExpression): Result<Type> {
893
+ const ctorScheme = this.signatureMap.get(node.name.value);
894
+ if (!ctorScheme) {
895
+ return {
896
+ success: false,
897
+ error: `Unknown constructor: ${node.name.value}`,
898
+ };
899
+ }
900
+
901
+ const ctorType = this.coreHM.instantiate(ctorScheme);
902
+
903
+ // Data constructors should be functions
904
+ if (!isFunctionType(ctorType))
905
+ return { success: false, error: "Constructors should be FunctionType" };
906
+
907
+ // Check arguments
908
+ let currentType: Type = ctorType;
909
+ for (const arg of node.contents) {
910
+ const argResult = arg.expression.accept(this);
911
+ if (!argResult.success) return argResult;
912
+
913
+ if (!isFunctionType(currentType)) {
914
+ return {
915
+ success: false,
916
+ error: `Too many arguments to constructor ${node.name.value}`,
917
+ };
918
+ }
919
+
920
+ const unifyResult = this.coreHM.unify(
921
+ argResult.value,
922
+ currentType.args[0]
923
+ );
924
+ if (!unifyResult.success) {
925
+ return {
926
+ success: false,
927
+ error: `Argument type mismatch for constructor ${node.name.value}`,
928
+ };
929
+ }
930
+
931
+ currentType = currentType.args[1];
932
+ }
933
+
934
+ return { success: true, value: currentType };
935
+ }
936
+ visitConsExpr(node: ConsExpression): Result<Type> {
937
+ const headResult = node.head.accept(this);
938
+ if (!headResult.success) return headResult;
939
+
940
+ const tailResult = node.tail.accept(this);
941
+ if (!tailResult.success) return tailResult;
942
+
943
+ // Create a list type with a fresh element type
944
+ const elemType = this.coreHM.freshVar();
945
+ const listT: TypeConstructor = listType(elemType);
946
+ // Unify the tail result with the list type
947
+ const unifyResult = this.coreHM.unify(tailResult.value, listT);
948
+ if (unifyResult.success === false) {
949
+ return {
950
+ success: false,
951
+ error: `Tail of cons must be a list: ${unifyResult.error}`,
952
+ };
953
+ }
954
+
955
+ // Now we know the tail is a list, so we can get the element type
956
+ const unifiedElemType = this.coreHM.applySubst(unifyResult.value, elemType);
957
+
958
+ // Head must match list element type
959
+ const headUnifyResult = this.coreHM.unify(
960
+ headResult.value,
961
+ unifiedElemType
962
+ );
963
+ if (headUnifyResult.success === false) {
964
+ return {
965
+ success: false,
966
+ error: `Head type doesn't match list element type: ${headUnifyResult.error}`,
967
+ };
968
+ }
969
+
970
+ // The result is the list type
971
+ return {
972
+ success: true,
973
+ value: this.coreHM.applySubst(unifyResult.value, listT),
974
+ };
975
+ }
976
+ visitLetInExpr(node: LetInExpression): Result<Type> {
977
+ const signatureMap = new Map();
978
+ node.declarations.statements.forEach((stmt) =>
979
+ stmt.accept(
980
+ new FunctionRegistrarVisitor(this.envs[0], signatureMap, this.coreHM)
981
+ )
982
+ );
983
+ const errors = [];
984
+ node.declarations.statements.forEach((stmt) =>
985
+ stmt.accept(
986
+ new FunctionCheckerVisitor(this.envs, signatureMap, this.coreHM, errors)
987
+ )
988
+ );
989
+ if (errors.length > 0) {
990
+ return { success: false, error: errors.join() };
991
+ }
992
+ return node.expression.accept(this);
993
+ }
994
+ visitOtherwise(node: Otherwise): Result<Type> {
995
+ return { success: true, value: booleanType };
996
+ }
997
+ visitCompositionExpression(node: CompositionExpression): Result<Type> {
998
+ const fResult = node.left.accept(this);
999
+ const gResult = node.right.accept(this);
1000
+
1001
+ if (!fResult.success) return fResult;
1002
+ if (!gResult.success) return gResult;
1003
+
1004
+ const a = this.coreHM.freshVar();
1005
+ const b = this.coreHM.freshVar();
1006
+ const c = this.coreHM.freshVar();
1007
+
1008
+ const fType: TypeConstructor = functionType(b, c);
1009
+ const gType: TypeConstructor = functionType(a, b);
1010
+
1011
+ const fSub = this.coreHM.unify(fResult.value, fType);
1012
+ const gSub = this.coreHM.unify(gResult.value, gType);
1013
+
1014
+ if (!fSub.success)
1015
+ return {
1016
+ success: false,
1017
+ error: "Left operand of composition must be a function",
1018
+ };
1019
+
1020
+ if (!gSub.success)
1021
+ return {
1022
+ success: false,
1023
+ error: "Right operand of composition must be a function",
1024
+ };
1025
+
1026
+ const composedType: TypeConstructor = functionType(a, c);
1027
+
1028
+ return { success: true, value: composedType };
1029
+ }
1030
+ visitLambda(node: Lambda): Result<Type> {
1031
+ // Create fresh type variables for parameters
1032
+ const paramTypes = node.parameters.map(() => this.coreHM.freshVar());
1033
+ this.envs.unshift(new Map());
1034
+
1035
+ // Add parameters to environment
1036
+ node.parameters.forEach((param, i) => {
1037
+ try {
1038
+ param.accept(
1039
+ new PatternVisitor(
1040
+ this.coreHM,
1041
+ this.signatureMap,
1042
+ paramTypes[i],
1043
+ this.envs,
1044
+ this
1045
+ )
1046
+ );
1047
+ } catch (error) {
1048
+ return {
1049
+ success: false,
1050
+ error: error.message,
1051
+ };
1052
+ }
1053
+ });
1054
+
1055
+ // Infer body type
1056
+ const inferrer = new InferenceEngine(
1057
+ this.signatureMap,
1058
+ this.coreHM,
1059
+ this.envs
1060
+ );
1061
+ const bodyResult = node.body.accept(inferrer);
1062
+ if (!bodyResult.success) return bodyResult;
1063
+
1064
+ // Construct function type
1065
+ const funcType = paramTypes.reduceRight(
1066
+ (acc, param) => functionType(param, acc),
1067
+ bodyResult.value
1068
+ );
1069
+
1070
+ return { success: true, value: funcType };
1071
+ }
1072
+ visitApplication(node: Application): Result<Type> {
1073
+ const funcResult = node.functionExpr.accept(this);
1074
+ if (funcResult.success === false) return funcResult;
1075
+
1076
+ const argResult = node.parameter.accept(this);
1077
+ if (argResult.success === false) return argResult;
1078
+
1079
+ const resultType = this.coreHM.freshVar();
1080
+ const funcType: TypeConstructor = functionType(argResult.value, resultType);
1081
+ const unifyResult = this.coreHM.unify(funcResult.value, funcType);
1082
+ if (unifyResult.success === false) {
1083
+ return {
1084
+ success: false,
1085
+ error: `Cannot apply ${showType(argResult.value)} to type ${showType(
1086
+ funcResult.value
1087
+ )}`,
1088
+ };
1089
+ }
1090
+ const substResultType = this.coreHM.applySubst(
1091
+ unifyResult.value,
1092
+ resultType
1093
+ );
1094
+ return { success: true, value: substResultType };
1095
+ }
1096
+ visitYield(node: Yield): Result<Type> {
1097
+ throw new Error("Method not implemented.");
1098
+ }
1099
+ visitRaise(node: Raise): Result<Type> {
1100
+ const bodyResult = node.body.accept(this);
1101
+ if (!bodyResult.success) return bodyResult;
1102
+
1103
+ const unifyResult = this.coreHM.unify(stringType, bodyResult.value);
1104
+ if (!unifyResult.success)
1105
+ return {
1106
+ success: false,
1107
+ error: "Body of Raise expression must be a YuString",
1108
+ };
1109
+
1110
+ return { success: true, value: this.coreHM.freshVar() };
1111
+ }
1112
+ visitIf(node: If): Result<Type> {
1113
+ const condResult = node.condition.accept(this);
1114
+ if (!condResult.success) return condResult;
1115
+
1116
+ const condSub = this.coreHM.unify(condResult.value, booleanType);
1117
+ if (!condSub.success)
1118
+ return { success: false, error: "Condition must be a boolean" };
1119
+
1120
+ const thenResult = node.then.accept(this);
1121
+ if (!thenResult.success) return thenResult;
1122
+
1123
+ const elseResult = node.elseExpr.accept(this);
1124
+ if (!elseResult.success) return elseResult;
1125
+
1126
+ const unifyResult = this.coreHM.unify(thenResult.value, elseResult.value);
1127
+ if (!unifyResult.success)
1128
+ return {
1129
+ success: false,
1130
+ error: `Branch types don't match: ${showType(
1131
+ thenResult.value
1132
+ )} vs ${showType(elseResult.value)}`,
1133
+ };
1134
+
1135
+ return thenResult;
1136
+ }
1137
+ // visitGuardedBody(node: GuardedBody): Result<Type> {
1138
+ // return node.body.accept(this);
1139
+ // }
1140
+
1141
+ visitReturn(node: Return): Result<Type> {
1142
+ return node.body.accept(this);
1143
+ }
1144
+ visitTypeCast(node: TypeCast): Result<Type> {
1145
+ return {
1146
+ success: true,
1147
+ value: new TypeBuilder(this.coreHM).build(node.body).type,
1148
+ };
1149
+ }
1150
+ visitPrint(node: Print): Result<Type> {
1151
+ const exprResult = node.expression.accept(this);
1152
+ if (exprResult.success === false) return exprResult;
1153
+
1154
+ const t1 = this.coreHM.freshVar(["Show"]);
1155
+
1156
+ const unifyResult = this.coreHM.unify(t1, exprResult.value);
1157
+ if (unifyResult.success === false) return unifyResult;
1158
+
1159
+ return { success: true, value: stringType };
1160
+ }
1161
+ visitListComprehension(node: ListComprehension): Result<Type> {
1162
+ // Generator(s) must unify with 'YuBoolean'
1163
+ for (const generator of node.generators) {
1164
+ const inferGenResult = generator.accept(this);
1165
+ if (inferGenResult.success === false) return inferGenResult;
1166
+ const unifyGenResult = this.coreHM.unify(
1167
+ inferGenResult.value,
1168
+ booleanType
1169
+ );
1170
+ if (unifyGenResult.success === false) return unifyGenResult;
1171
+ }
1172
+
1173
+ // The projection must unify to 'a'
1174
+ const exprResult = node.projection.accept(this);
1175
+ if (exprResult.success === false) return exprResult;
1176
+
1177
+ const list = listType(exprResult.value);
1178
+ return { success: true, value: list };
1179
+ }
1180
+ visitGenerator(node: Generator): Result<Type> {
1181
+ // A generator must unify to a 'YuList a'
1182
+ const inferResult = node.expression.accept(this);
1183
+ if (inferResult.success === false) return inferResult;
1184
+
1185
+ const elemType = this.coreHM.freshVar();
1186
+ const genericList = listType(elemType);
1187
+
1188
+ const unifyResult = this.coreHM.unify(inferResult.value, genericList);
1189
+ if (unifyResult.success === false) return unifyResult;
1190
+
1191
+ const subsElemType = this.coreHM.applySubst(unifyResult.value, elemType);
1192
+ const schemeElemType = this.coreHM.generalize(this.envs[0], subsElemType);
1193
+
1194
+ const bindingName = node.variable.value;
1195
+ if (this.envs[0].has(bindingName))
1196
+ return {
1197
+ success: false,
1198
+ error: `Multiple declarations of '${bindingName}'`,
1199
+ };
1200
+ this.envs[0].set(bindingName, schemeElemType);
1201
+ return { success: true, value: booleanType }; // Shady af but helps unify only with booleanType in visitListComprehension
1202
+ }
1203
+ visitFor(node: For): Result<Type> {
1204
+ throw new Error("Method not implemented.");
1205
+ }
1206
+ visitSwitch(node: Switch): Result<Type> {
1207
+ const firstBranch = node.cases[0];
1208
+
1209
+ // Infer type of case key
1210
+ const caseResult = node.value.accept(this);
1211
+ if (caseResult.success === false) return caseResult;
1212
+ // Unify first branch condition with case key
1213
+ try {
1214
+ firstBranch.condition.accept(
1215
+ new PatternVisitor(
1216
+ this.coreHM,
1217
+ this.signatureMap,
1218
+ caseResult.value,
1219
+ this.envs,
1220
+ this
1221
+ )
1222
+ );
1223
+ } catch (error) {
1224
+ return { success: false, error: error.message };
1225
+ }
1226
+
1227
+ // Infer first branch result
1228
+ const firstBranchType = firstBranch.body.accept(this);
1229
+ if (firstBranchType.success === false) return firstBranchType;
1230
+
1231
+ // Every branch should return same type as the first branch
1232
+ for (const branch of node.cases.slice(1)) {
1233
+ // Unify condition with case key
1234
+ try {
1235
+ branch.condition.accept(
1236
+ new PatternVisitor(
1237
+ this.coreHM,
1238
+ this.signatureMap,
1239
+ caseResult.value,
1240
+ this.envs,
1241
+ this
1242
+ )
1243
+ );
1244
+ } catch (error) {
1245
+ return { success: false, error: error.message };
1246
+ }
1247
+ // Unify branch result with first branch
1248
+ const branchType = branch.body.accept(this);
1249
+ if (branchType.success === false) return branchType;
1250
+
1251
+ const unifyBranchResult = this.coreHM.unify(
1252
+ firstBranchType.value,
1253
+ branchType.value
1254
+ );
1255
+ if (unifyBranchResult.success === false) return unifyBranchResult;
1256
+ }
1257
+ return firstBranchType;
1258
+ }
1259
+ visitRangeExpression(node: RangeExpression): Result<Type> {
1260
+ const startResult = node.start.accept(this);
1261
+ if (!startResult.success) return startResult;
1262
+
1263
+ const endResult = node.end.accept(this);
1264
+ if (!endResult.success) return endResult;
1265
+ const rangeElemType = this.coreHM.freshVar(["Ord", "Enum"]);
1266
+
1267
+ // Unify both start and end with this constrained type
1268
+ const unifyStart = this.coreHM.unify(startResult.value, rangeElemType);
1269
+ if (!unifyStart.success) {
1270
+ return {
1271
+ success: false,
1272
+ error: `Range start must be of an enumerable and orderable type, got ${showType(
1273
+ startResult.value
1274
+ )}`,
1275
+ };
1276
+ }
1277
+
1278
+ // Apply substitution from start unification to end type before unifying
1279
+ const substitutedEnd = this.coreHM.applySubst(
1280
+ unifyStart.value,
1281
+ endResult.value
1282
+ );
1283
+ const unifyEnd = this.coreHM.unify(
1284
+ substitutedEnd,
1285
+ this.coreHM.applySubst(unifyStart.value, rangeElemType)
1286
+ );
1287
+ if (!unifyEnd.success) {
1288
+ return {
1289
+ success: false,
1290
+ error: `Range end must match start type; expected ${showType(
1291
+ startResult.value
1292
+ )}, got ${showType(endResult.value)}`,
1293
+ };
1294
+ }
1295
+
1296
+ // Combine substitutions
1297
+ const combinedSubst = this.coreHM.composeSubst(
1298
+ unifyEnd.value,
1299
+ unifyStart.value
1300
+ );
1301
+ const finalElemType = this.coreHM.applySubst(combinedSubst, rangeElemType);
1302
+
1303
+ // Result is a list of the element type
1304
+ return {
1305
+ success: true,
1306
+ value: listType(finalElemType),
1307
+ };
1308
+ }
1309
+ visit(node: ASTNode): Result<Type> {
1310
+ return node.accept(this);
1311
+ }
1312
+ }
1313
+
1314
+ const searchInEnvironments = (
1315
+ envs: Environment[],
1316
+ key: string
1317
+ ): Result<TypeScheme> => {
1318
+ let result;
1319
+ for (const env of envs) {
1320
+ if (env.has(key)) {
1321
+ result = env.get(key);
1322
+ break;
1323
+ }
1324
+ }
1325
+ if (!result) return { success: false, error: `Unbound variable '${key}'` };
1326
+ return { success: true, value: result };
1327
+ };