yukigo-haskell-parser 0.1.3 → 0.2.2

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