diff --git a/src/typeck.test.ts b/src/typeck.test.ts index 1a2101b..bf79aee 100644 --- a/src/typeck.test.ts +++ b/src/typeck.test.ts @@ -1,4 +1,4 @@ -import { TY_INT, TY_STRING } from "./ast"; +import { TY_INT, TY_STRING, TY_UNIT } from "./ast"; import { DUMMY_SPAN as SPAN } from "./error"; import { InferContext } from "./typeck"; @@ -34,3 +34,21 @@ it("should conflict assignments to resolvable type vars", () => { expect(() => infcx.assign(a, TY_STRING, SPAN)).toThrow(); }); + +it("should not cycle", () => { + const infcx = new InferContext(); + + const a = infcx.newVar(); + const b = infcx.newVar(); + + infcx.assign(a, b, SPAN); + infcx.assign(b, a, SPAN); + + const aType = infcx.resolveIfPossible(a); + expect(aType.kind).toEqual("var"); + + infcx.assign(a, TY_UNIT, SPAN); + + const bType = infcx.resolveIfPossible(b); + expect(bType.kind).toEqual("tuple"); +}); diff --git a/src/typeck.ts b/src/typeck.ts index 8dcdb91..0d4e2eb 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -283,21 +283,32 @@ export class InferContext { } } + private findRoot(variable: number): number { + let root = variable; + let nextVar; + while ((nextVar = this.tyVars[root]).kind === "unified") { + root = nextVar.index; + } + return root; + } + /** * Try to constrain a type variable to be of a specific type. * INVARIANT: Both sides must not be of res "final", use resolveIfPossible * before calling this. */ private constrainVar(variable: number, ty: Ty) { - let root = variable; - let nextVar; - while ((nextVar = this.tyVars[root]).kind === "unified") { - root = nextVar.index; - } + let root = this.findRoot(variable); if (ty.kind === "var") { - // Point the lhs to the rhs. - this.tyVars[root] = { kind: "unified", index: ty.index }; + // Now we point our root to the other root to unify the two graphs. + const otherRoot = this.findRoot(ty.index); + + // If both types have the same root, we don't need to do anything + // as they're already part of the same graph. + if (root != otherRoot) { + this.tyVars[root] = { kind: "unified", index: otherRoot }; + } } else { this.tyVars[root] = { kind: "final", ty }; }