@samitouri / QOS-React-2 / commits / bb2325bdce

[typer] Track return type in FunctionType

Rather than having a special FunctionCall type that deduces the return type, change the FunctionType to include the return type. This return type is inferred as part of unification.

Sathya Gunasekaran committed Mar 27, 2023 at 15:58 UTC bb2325bdce7b4f588df01829c0641b22e86bc25a
3 files changed +38 -36
compiler/forget/src/HIR/ObjectShape.ts
+4
@@ -28,6 +28,7 @@ function addFunction(
28 addShape(registry, shapeId, properties, fn);
29 return {
30 kind: "Function",
31 + return: fn.returnType,
32 shapeId,
33 };
34 }
@@ -63,7 +64,10 @@ export type FunctionSignature = {
64 };
65
66 export type ObjectShape = {
67 + // TODO(gsn): When can the key be null here?
68 properties: Map<string, BuiltInType | null>;
69 + // TODO(gsn): Why do Objects have a `functionType`? Oh, this the constructor.
70 + // Let's rename to constructor?
71 functionType: FunctionSignature | null;
72 };
73
compiler/forget/src/HIR/Types.ts
+17 -3
@@ -9,7 +9,13 @@ import { Hook } from "./Hooks";
9
10 export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
11
12 -export type Type = BuiltInType | HookType | PhiType | TypeVar | PolyType;
12 +export type Type =
13 + | BuiltInType
14 + | HookType
15 + | PhiType
16 + | TypeVar
17 + | PolyType
18 + | PropType;
19 export type PrimitiveType = { kind: "Primitive" };
20 export type HookType = {
21 kind: "Hook";
@@ -30,6 +36,7 @@ export type HookType = {
36 export type FunctionType = {
37 kind: "Function";
38 shapeId: string | null;
39 + return: Type;
40 };
41
42 export type ObjectType = {
@@ -48,7 +55,11 @@ export type PhiType = {
55 kind: "Phi";
56 operands: Array<Type>;
57 };
51 -
58 +export type PropType = {
59 + kind: "Property";
60 + object: Type;
61 + propertyName: string;
62 +};
63 /**
64 * Simulated opaque type for TypeId to prevent using normal numbers as ids
65 * accidentally.
@@ -109,7 +120,10 @@ function objectTypeEquals(tA: Type, tB: Type): boolean {
120 }
121
122 function funcTypeEquals(tA: Type, tB: Type): boolean {
112 - return typeKindCheck(tA, tB, "Function");
123 + if (tA.kind !== "Function" || tB.kind !== "Function") {
124 + return false;
125 + }
126 + return typeEquals(tA.return, tB.return);
127 }
128
129 function hookTypeEquals(tA: Type, tB: Type): boolean {
compiler/forget/src/TypeInference/InferTypes.ts
+17 -33
@@ -63,25 +63,12 @@ function apply(func: HIRFunction, unifier: Unifier): void {
63 }
64 }
65
66 -type FunctionCallType = {
67 - kind: "FunctionCall";
68 - returnType: TypeVar;
69 -};
70 -
71 -type PolyType =
72 - | {
73 - kind: "Property";
74 - object: Type;
75 - propertyName: string;
76 - }
77 - | FunctionCallType;
78 -
66 type TypeEquation = {
67 left: Type;
81 - right: Type | PolyType;
68 + right: Type;
69 };
70
84 -function equation(left: Type, right: Type | PolyType): TypeEquation {
71 +function equation(left: Type, right: Type): TypeEquation {
72 return {
73 left,
74 right,
@@ -165,7 +152,7 @@ function* generateInstructionTypes(
152 if (hook !== null) {
153 type = { kind: "Hook", definition: hook };
154 } else {
168 - type = { kind: "Function", shapeId: null };
155 + type = { kind: "Function", shapeId: null, return: left };
156 }
157 yield equation(value.callee.identifier.type, type);
158 break;
@@ -193,9 +180,11 @@ function* generateInstructionTypes(
180 case "MethodCall": {
181 const returnType = makeType();
182 yield equation(value.property.identifier.type, {
196 - kind: "FunctionCall",
197 - returnType,
183 + kind: "Function",
184 + return: returnType,
185 + shapeId: null,
186 });
187 +
188 yield equation(left, returnType);
189 }
190 }
@@ -210,18 +199,7 @@ class Unifier {
199 this.env = env;
200 }
201
213 - unifyFunctionCall(tA: Type, tB: FunctionCallType): void {
214 - const propertyType = this.get(tA);
215 - if (propertyType.kind === "Function") {
216 - const fn = this.env.getFunctionSignature(propertyType);
217 - const returnType = fn?.returnType ?? null;
218 - if (returnType !== null) {
219 - this.unify(tB.returnType, returnType);
220 - }
221 - }
222 - }
223 -
224 - unify(tA: Type, tB: Type | PolyType): void {
202 + unify(tA: Type, tB: Type): void {
203 if (tB.kind === "Property") {
204 const objectType = this.get(tB.object);
205 if (objectType.kind === "Object" || objectType.kind === "Function") {
@@ -236,9 +214,6 @@ class Unifier {
214 // We do not error if tB is not a known object or function (even if it
215 // is a primitive), since JS implicit conversion to objects
216 return;
239 - } else if (tB.kind === "FunctionCall") {
240 - this.unifyFunctionCall(tA, tB);
241 - return;
217 }
218
219 if (typeEquals(tA, tB)) {
@@ -254,6 +229,11 @@ class Unifier {
229 this.bindVariableTo(tB, tA);
230 return;
231 }
232 +
233 + if (tB.kind === "Function" && tA.kind === "Function") {
234 + this.unify(tA.return, tB.return);
235 + return;
236 + }
237 }
238
239 bindVariableTo(v: TypeVar, type: Type): void {
@@ -298,6 +278,10 @@ class Unifier {
278 return type.operands.some((o) => this.occursCheck(v, o));
279 }
280
281 + if (type.kind === "Function") {
282 + return this.occursCheck(v, type.return);
283 + }
284 +
285 return false;
286 }
287