483
}
484
485
if (type.kind === 'Phi') {
486
- const operands = new Set(type.operands.map(i => this.get(i).kind));
487
-
488
- CompilerError.invariant(operands.size > 0, {
486
+ CompilerError.invariant(type.operands.length > 0, {
487
reason: 'there should be at least one operand',
488
description: null,
489
loc: null,
490
suggestions: null,
491
});
494
- const kind = operands.values().next().value;
492
496
- // there's only one unique type and it's not a type var
497
- if (operands.size === 1 && kind !== 'Type') {
498
- this.unify(v, type.operands[0]);
493
+ let candidateType: Type | null = null;
494
+ for (const operand of type.operands) {
495
+ const resolved = this.get(operand);
496
+ if (candidateType === null) {
497
+ candidateType = resolved;
498
+ } else if (!typeEquals(resolved, candidateType)) {
499
+ candidateType = null;
500
+ break;
501
+ } // else same type, continue
502
+ }
503
+
504
+ if (candidateType !== null) {
505
+ this.unify(v, candidateType);
506
return;
507
}
508
}
509
510
if (this.occursCheck(v, type)) {
511
+ const resolvedType = this.tryResolveType(v, type);
512
+ if (resolvedType !== null) {
513
+ this.substitutions.set(v.id, resolvedType);
514
+ return;
515
+ }
516
throw new Error('cycle detected');
517
}
518
519
this.substitutions.set(v.id, type);
520
}
521
522
+ tryResolveType(v: TypeVar, type: Type): Type | null {
523
+ switch (type.kind) {
524
+ case 'Phi': {
525
+ /**
526
+ * Resolve the type of the phi by recursively removing `v` as an operand.
527
+ * For example we can end up with types like this:
528
+ *
529
+ * v = Phi [
530
+ * T1
531
+ * T2
532
+ * Phi [
533
+ * T3
534
+ * Phi [
535
+ * T4
536
+ * v <-- cycle!
537
+ * ]
538
+ * ]
539
+ * ]
540
+ *
541
+ * By recursively removing `v`, we end up with:
542
+ *
543
+ * v = Phi [
544
+ * T1
545
+ * T2
546
+ * Phi [
547
+ * T3
548
+ * Phi [
549
+ * T4
550
+ * ]
551
+ * ]
552
+ * ]
553
+ *
554
+ * Which avoids the cycle
555
+ */
556
+ const operands = [];
557
+ for (const operand of type.operands) {
558
+ if (operand.kind === 'Type' && operand.id === v.id) {
559
+ continue;
560
+ }
561
+ const resolved = this.tryResolveType(v, operand);
562
+ if (resolved === null) {
563
+ return null;
564
+ }
565
+ operands.push(resolved);
566
+ }
567
+ return {kind: 'Phi', operands};
568
+ }
569
+ case 'Type': {
570
+ const substitution = this.get(type);
571
+ if (substitution !== type) {
572
+ const resolved = this.tryResolveType(v, substitution);
573
+ if (resolved !== null) {
574
+ this.substitutions.set(type.id, resolved);
575
+ }
576
+ return resolved;
577
+ }
578
+ return type;
579
+ }
580
+ case 'Property': {
581
+ const objectType = this.tryResolveType(v, this.get(type.objectType));
582
+ if (objectType === null) {
583
+ return null;
584
+ }
585
+ return {
586
+ kind: 'Property',
587
+ objectName: type.objectName,
588
+ objectType,
589
+ propertyName: type.propertyName,
590
+ };
591
+ }
592
+ case 'Function': {
593
+ const returnType = this.tryResolveType(v, this.get(type.return));
594
+ if (returnType === null) {
595
+ return null;
596
+ }
597
+ return {
598
+ kind: 'Function',
599
+ return: returnType,
600
+ shapeId: type.shapeId,
601
+ };
602
+ }
603
+ case 'ObjectMethod':
604
+ case 'Object':
605
+ case 'Primitive':
606
+ case 'Poly': {
607
+ return type;
608
+ }
609
+ default: {
610
+ assertExhaustive(type, `Unexpected type kind '${(type as any).kind}'`);
611
+ }
612
+ }
613
+ }
614
+
615
occursCheck(v: TypeVar, type: Type): boolean {
616
if (typeEquals(v, type)) return true;
617