[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