mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
typeck cleanup
This commit is contained in:
parent
73a369730b
commit
ba3a199249
9 changed files with 345 additions and 319 deletions
|
|
@ -52,6 +52,7 @@ module.exports = {
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
"@typescript-eslint/no-unsafe-argument": "off",
|
"@typescript-eslint/no-unsafe-argument": "off",
|
||||||
|
"typescript-eslint/no-unsafe-call": "off",
|
||||||
|
|
||||||
// Useful extra lints that are not on by default:
|
// Useful extra lints that are not on by default:
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "warn",
|
"@typescript-eslint/explicit-module-boundary-types": "warn",
|
||||||
|
|
|
||||||
|
|
@ -505,7 +505,8 @@ export type TyVar = {
|
||||||
|
|
||||||
export type TyStruct = {
|
export type TyStruct = {
|
||||||
kind: "struct";
|
kind: "struct";
|
||||||
name: string;
|
itemId: ItemId,
|
||||||
|
_name: string;
|
||||||
fields: [string, Ty][];
|
fields: [string, Ty][];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { TY_I32, TY_INT, TyStruct } from "./ast";
|
import { ItemId, TY_I32, TY_INT, TyStruct } from "./ast";
|
||||||
import { layoutOfStruct } from "./codegen";
|
import { layoutOfStruct } from "./codegen";
|
||||||
|
|
||||||
it("should compute struct layout correctly", () => {
|
it("should compute struct layout correctly", () => {
|
||||||
const ty: TyStruct = {
|
const ty: TyStruct = {
|
||||||
kind: "struct",
|
kind: "struct",
|
||||||
name: "",
|
itemId: ItemId.dummy(),
|
||||||
|
_name: "",
|
||||||
fields: [
|
fields: [
|
||||||
["uwu", TY_I32],
|
["uwu", TY_I32],
|
||||||
["owo", TY_INT],
|
["owo", TY_INT],
|
||||||
|
|
@ -48,7 +49,8 @@ it("should compute struct layout correctly", () => {
|
||||||
it("should compute single field struct layout correctly", () => {
|
it("should compute single field struct layout correctly", () => {
|
||||||
const ty: TyStruct = {
|
const ty: TyStruct = {
|
||||||
kind: "struct",
|
kind: "struct",
|
||||||
name: "",
|
itemId: ItemId.dummy(),
|
||||||
|
_name: "",
|
||||||
fields: [["owo", TY_INT]],
|
fields: [["owo", TY_INT]],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -426,8 +426,9 @@ function lowerExpr(
|
||||||
lowerExpr(fcx, instrs, expr.rhs);
|
lowerExpr(fcx, instrs, expr.rhs);
|
||||||
const { lhs } = expr;
|
const { lhs } = expr;
|
||||||
switch (lhs.kind) {
|
switch (lhs.kind) {
|
||||||
case "ident": {
|
case "ident":
|
||||||
const res = lhs.value.res;
|
case "path": {
|
||||||
|
const res = lhs.kind === "path" ? lhs.res : lhs.value.res;
|
||||||
|
|
||||||
switch (res.kind) {
|
switch (res.kind) {
|
||||||
case "local": {
|
case "local": {
|
||||||
|
|
|
||||||
|
|
@ -119,3 +119,9 @@ export function lines(file: LoadedFile): Span[] {
|
||||||
function min(a: number, b: number): number {
|
function min(a: number, b: number): number {
|
||||||
return a < b ? a : b;
|
return a < b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function unreachable(msg?: string): never {
|
||||||
|
throw new Error(
|
||||||
|
`entered unreachable code${msg !== undefined ? `: ${msg}` : ""}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,9 @@ import { loadCrate } from "./loader";
|
||||||
const INPUT = `
|
const INPUT = `
|
||||||
type A = struct { a: Int };
|
type A = struct { a: Int };
|
||||||
|
|
||||||
type What = What;
|
|
||||||
|
|
||||||
type Uwu = (Int, Int);
|
type Uwu = (Int, Int);
|
||||||
|
|
||||||
function main() = (
|
function main() = (
|
||||||
let a: What = 0;
|
|
||||||
uwu();
|
uwu();
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -41,6 +38,10 @@ function test(b: B) = (
|
||||||
b.a;
|
b.a;
|
||||||
);
|
);
|
||||||
|
|
||||||
|
mod aa (
|
||||||
|
global UWU: Int = 0;
|
||||||
|
);
|
||||||
|
|
||||||
function eat(a: A) =;
|
function eat(a: A) =;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,7 @@ export function printTy(ty: Ty): string {
|
||||||
return `?${ty.index}`;
|
return `?${ty.index}`;
|
||||||
}
|
}
|
||||||
case "struct": {
|
case "struct": {
|
||||||
return ty.name;
|
return ty._name;
|
||||||
}
|
}
|
||||||
case "never": {
|
case "never": {
|
||||||
return "!";
|
return "!";
|
||||||
|
|
|
||||||
489
src/typeck.ts
489
src/typeck.ts
|
|
@ -9,7 +9,6 @@ import {
|
||||||
ExprUnary,
|
ExprUnary,
|
||||||
foldAst,
|
foldAst,
|
||||||
Folder,
|
Folder,
|
||||||
IdentWithRes,
|
|
||||||
ItemId,
|
ItemId,
|
||||||
LOGICAL_KINDS,
|
LOGICAL_KINDS,
|
||||||
LoopId,
|
LoopId,
|
||||||
|
|
@ -30,12 +29,25 @@ import {
|
||||||
Item,
|
Item,
|
||||||
StructLiteralField,
|
StructLiteralField,
|
||||||
superFoldExpr,
|
superFoldExpr,
|
||||||
|
ExprCall,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { GlobalContext } from "./context";
|
import { GlobalContext } from "./context";
|
||||||
import { CompilerError, Span } from "./error";
|
import { CompilerError, Span, unreachable } from "./error";
|
||||||
import { printTy } from "./printer";
|
import { printTy } from "./printer";
|
||||||
import { ComplexMap } from "./utils";
|
import { ComplexMap } from "./utils";
|
||||||
|
|
||||||
|
type TypeckCtx = {
|
||||||
|
gcx: GlobalContext;
|
||||||
|
/**
|
||||||
|
* A cache of all item types.
|
||||||
|
* Starts off as undefined, then gets set to null
|
||||||
|
* while computing the type (for cycle detection) and
|
||||||
|
* afterwards, we get the ty.
|
||||||
|
*/
|
||||||
|
itemTys: ComplexMap<ItemId, Ty | null>;
|
||||||
|
ast: Crate<Resolved>;
|
||||||
|
};
|
||||||
|
|
||||||
function mkTyFn(params: Ty[], returnTy: Ty): Ty {
|
function mkTyFn(params: Ty[], returnTy: Ty): Ty {
|
||||||
return { kind: "fn", params, returnTy };
|
return { kind: "fn", params, returnTy };
|
||||||
}
|
}
|
||||||
|
|
@ -90,27 +102,33 @@ function typeOfBuiltinValue(name: BuiltinName, span: Span): Ty {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused.
|
// TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused.
|
||||||
function lowerAstTyBase(
|
function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty {
|
||||||
type: Type<Resolved>,
|
|
||||||
lowerIdentTy: (ident: IdentWithRes<Resolved>) => Ty,
|
|
||||||
typeOfItem: (itemId: ItemId, cause: Span) => Ty,
|
|
||||||
): Ty {
|
|
||||||
switch (type.kind) {
|
switch (type.kind) {
|
||||||
case "ident": {
|
case "ident": {
|
||||||
return lowerIdentTy(type.value);
|
const ident = type.value;
|
||||||
|
const res = ident.res;
|
||||||
|
switch (res.kind) {
|
||||||
|
case "local": {
|
||||||
|
throw new Error("Item type cannot refer to local variable");
|
||||||
|
}
|
||||||
|
case "item": {
|
||||||
|
return typeOfItem(cx, res.id, type.span);
|
||||||
|
}
|
||||||
|
case "builtin": {
|
||||||
|
return builtinAsTy(res.name, ident.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "list": {
|
case "list": {
|
||||||
return {
|
return {
|
||||||
kind: "list",
|
kind: "list",
|
||||||
elem: lowerAstTyBase(type.elem, lowerIdentTy, typeOfItem),
|
elem: lowerAstTy(cx, type.elem),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "tuple": {
|
case "tuple": {
|
||||||
return {
|
return {
|
||||||
kind: "tuple",
|
kind: "tuple",
|
||||||
elems: type.elems.map((type) =>
|
elems: type.elems.map((type) => lowerAstTy(cx, type)),
|
||||||
lowerAstTyBase(type, lowerIdentTy, typeOfItem),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "never": {
|
case "never": {
|
||||||
|
|
@ -119,15 +137,11 @@ function lowerAstTyBase(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function typeck(
|
function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty {
|
||||||
gcx: GlobalContext,
|
if (itemId.crateId !== cx.ast.id) {
|
||||||
ast: Crate<Resolved>,
|
// Look up foreign items in the foreign crates, we don't need to lower those
|
||||||
): Crate<Typecked> {
|
// ourselves.
|
||||||
const itemTys = new ComplexMap<ItemId, Ty | null>();
|
const item = cx.gcx.findItem(itemId);
|
||||||
|
|
||||||
function typeOfItem(itemId: ItemId, cause: Span): Ty {
|
|
||||||
if (itemId.crateId !== ast.id) {
|
|
||||||
const item = gcx.findItem(itemId, ast);
|
|
||||||
|
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case "function":
|
case "function":
|
||||||
|
|
@ -150,54 +164,59 @@ export function typeck(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = gcx.findItem(itemId, ast);
|
const item = cx.gcx.findItem(itemId, cx.ast);
|
||||||
const ty = itemTys.get(itemId);
|
const cachedTy = cx.itemTys.get(itemId);
|
||||||
if (ty) {
|
if (cachedTy) {
|
||||||
return ty;
|
return cachedTy;
|
||||||
}
|
}
|
||||||
if (ty === null) {
|
if (cachedTy === null) {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
`cycle computing type of #G${itemId.toString()}`,
|
`cycle computing type of #G${itemId.toString()}`,
|
||||||
item.span,
|
item.span,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
itemTys.set(itemId, null);
|
cx.itemTys.set(itemId, null);
|
||||||
|
|
||||||
|
let ty: Ty;
|
||||||
|
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case "function":
|
case "function":
|
||||||
case "import": {
|
case "import": {
|
||||||
const args = item.params.map((arg) => lowerAstTy(arg.type));
|
const args = item.params.map((arg) => lowerAstTy(cx, arg.type));
|
||||||
const returnTy: Ty = item.returnType
|
const returnTy: Ty = item.returnType
|
||||||
? lowerAstTy(item.returnType)
|
? lowerAstTy(cx, item.returnType)
|
||||||
: TY_UNIT;
|
: TY_UNIT;
|
||||||
|
|
||||||
const ty: Ty = { kind: "fn", params: args, returnTy };
|
ty = { kind: "fn", params: args, returnTy };
|
||||||
itemTys.set(item.id, ty);
|
break;
|
||||||
return ty;
|
|
||||||
}
|
}
|
||||||
case "type": {
|
case "type": {
|
||||||
switch (item.type.kind) {
|
switch (item.type.kind) {
|
||||||
case "struct": {
|
case "struct": {
|
||||||
const ty: Ty = {
|
ty = {
|
||||||
kind: "struct",
|
kind: "struct",
|
||||||
name: item.name,
|
itemId: item.id,
|
||||||
|
_name: item.name,
|
||||||
fields: [
|
fields: [
|
||||||
/*dummy*/
|
/*dummy*/
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
// Set it here already to allow for recursive types.
|
||||||
itemTys.set(item.id, ty);
|
cx.itemTys.set(item.id, ty);
|
||||||
|
|
||||||
const fields = item.type.fields.map<[string, Ty]>(
|
const fields = item.type.fields.map<[string, Ty]>(
|
||||||
({ name, type }) => [name.name, lowerAstTy(type)],
|
({ name, type }) => [name.name, lowerAstTy(cx, type)],
|
||||||
);
|
);
|
||||||
|
|
||||||
ty.fields = fields;
|
ty.fields = fields;
|
||||||
return ty;
|
break;
|
||||||
}
|
}
|
||||||
case "alias": {
|
case "alias": {
|
||||||
return lowerAstTy(item.type.type);
|
ty = lowerAstTy(cx, item.type.type);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case "mod": {
|
case "mod": {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
|
|
@ -212,60 +231,43 @@ export function typeck(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "global": {
|
case "global": {
|
||||||
const ty = lowerAstTy(item.type);
|
ty = lowerAstTy(cx, item.type);
|
||||||
itemTys.set(item.id, ty);
|
break;
|
||||||
return ty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function lowerAstTy(type: Type<Resolved>): Ty {
|
cx.itemTys.set(item.id, ty);
|
||||||
return lowerAstTyBase(
|
return ty;
|
||||||
type,
|
|
||||||
(ident) => {
|
|
||||||
const res = ident.res;
|
|
||||||
switch (res.kind) {
|
|
||||||
case "local": {
|
|
||||||
throw new Error("Item type cannot refer to local variable");
|
|
||||||
}
|
|
||||||
case "item": {
|
|
||||||
return typeOfItem(res.id, type.span);
|
|
||||||
}
|
|
||||||
case "builtin": {
|
|
||||||
return builtinAsTy(res.name, ident.span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
typeOfItem,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function typeck(
|
||||||
|
gcx: GlobalContext,
|
||||||
|
ast: Crate<Resolved>,
|
||||||
|
): Crate<Typecked> {
|
||||||
|
const cx = {
|
||||||
|
gcx,
|
||||||
|
itemTys: new ComplexMap<ItemId, Ty | null>(),
|
||||||
|
ast,
|
||||||
|
};
|
||||||
|
|
||||||
const checker: Folder<Resolved, Typecked> = {
|
const checker: Folder<Resolved, Typecked> = {
|
||||||
...mkDefaultFolder(),
|
...mkDefaultFolder(),
|
||||||
itemInner(item: Item<Resolved>): Item<Typecked> {
|
itemInner(item: Item<Resolved>): Item<Typecked> {
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case "function": {
|
case "function": {
|
||||||
const fnTy = typeOfItem(item.id, item.span) as TyFn;
|
const fnTy = typeOfItem(cx, item.id, item.span) as TyFn;
|
||||||
const body = checkBody(gcx, ast, item.body, fnTy, typeOfItem);
|
const body = checkBody(cx, ast, item.body, fnTy);
|
||||||
|
|
||||||
const returnType = item.returnType && {
|
|
||||||
...item.returnType,
|
|
||||||
ty: fnTy.returnTy,
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
params: item.params.map((arg, i) => ({
|
params: item.params.map((arg) => ({ ...arg })),
|
||||||
...arg,
|
|
||||||
type: { ...arg.type, ty: fnTy.params[i] },
|
|
||||||
})),
|
|
||||||
body,
|
body,
|
||||||
returnType,
|
|
||||||
ty: fnTy,
|
ty: fnTy,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "import": {
|
case "import": {
|
||||||
const fnTy = typeOfItem(item.id, item.span) as TyFn;
|
const fnTy = typeOfItem(cx, item.id, item.span) as TyFn;
|
||||||
|
|
||||||
fnTy.params.forEach((param, i) => {
|
fnTy.params.forEach((param, i) => {
|
||||||
switch (param.kind) {
|
switch (param.kind) {
|
||||||
|
|
@ -288,33 +290,23 @@ export function typeck(
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
`import return must be I32 or Int`,
|
`import return must be I32, Int or ()`,
|
||||||
item.returnType!.span,
|
item.returnType!.span,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const returnType = item.returnType && {
|
|
||||||
...item.returnType,
|
|
||||||
ty: fnTy.returnTy,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
kind: "import",
|
kind: "import",
|
||||||
module: item.module,
|
params: item.params.map((arg) => ({ ...arg })),
|
||||||
func: item.func,
|
|
||||||
name: item.name,
|
|
||||||
params: item.params.map((arg, i) => ({
|
|
||||||
...arg,
|
|
||||||
type: { ...arg.type, ty: fnTy.params[i] },
|
|
||||||
})),
|
|
||||||
returnType,
|
|
||||||
ty: fnTy,
|
ty: fnTy,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "type": {
|
case "type": {
|
||||||
|
const ty = typeOfItem(cx, item.id, item.span) as TyStruct;
|
||||||
|
|
||||||
switch (item.type.kind) {
|
switch (item.type.kind) {
|
||||||
case "struct": {
|
case "struct": {
|
||||||
const fieldNames = new Set();
|
const fieldNames = new Set();
|
||||||
|
|
@ -328,31 +320,20 @@ export function typeck(
|
||||||
fieldNames.add(name);
|
fieldNames.add(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ty = typeOfItem(item.id, item.span) as TyStruct;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
name: item.name,
|
|
||||||
type: {
|
type: {
|
||||||
kind: "struct",
|
kind: "struct",
|
||||||
fields: item.type.fields.map((field, i) => ({
|
fields: item.type.fields.map((field) => ({ ...field })),
|
||||||
name: field.name,
|
|
||||||
type: {
|
|
||||||
...field.type,
|
|
||||||
ty: ty.fields[i][1],
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
},
|
||||||
|
ty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "alias": {
|
case "alias": {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
name: item.name,
|
type: { ...item.type },
|
||||||
type: {
|
ty,
|
||||||
kind: "alias",
|
|
||||||
type: item.type.type,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -368,7 +349,7 @@ export function typeck(
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
case "global": {
|
case "global": {
|
||||||
const ty = typeOfItem(item.id, item.span);
|
const ty = typeOfItem(cx, item.id, item.span);
|
||||||
const { init } = item;
|
const { init } = item;
|
||||||
|
|
||||||
if (init.kind !== "literal" || init.value.kind !== "int") {
|
if (init.kind !== "literal" || init.value.kind !== "int") {
|
||||||
|
|
@ -405,15 +386,12 @@ export function typeck(
|
||||||
|
|
||||||
const main = typecked.rootItems.find((item) => {
|
const main = typecked.rootItems.find((item) => {
|
||||||
if (item.kind === "function" && item.name === "main") {
|
if (item.kind === "function" && item.name === "main") {
|
||||||
if (item.returnType !== undefined) {
|
if (!tyIsUnit(item.ty!.returnTy)) {
|
||||||
const ty = item.body.ty;
|
|
||||||
if (ty.kind !== "tuple" || ty.elems.length !== 0) {
|
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
`\`main\` has an invalid signature. main takes no arguments and returns nothing`,
|
`\`main\` has an invalid signature. main takes no arguments and returns nothing`,
|
||||||
item.span,
|
item.span,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -514,7 +492,7 @@ export class InferContext {
|
||||||
public resolveIfPossible(ty: Ty): Ty {
|
public resolveIfPossible(ty: Ty): Ty {
|
||||||
// TODO: dont be shallow resolve
|
// TODO: dont be shallow resolve
|
||||||
// note that fixing this will cause cycles. fix those cycles instead using
|
// note that fixing this will cause cycles. fix those cycles instead using
|
||||||
// he fancy occurs check as errs called it.
|
// the fancy occurs check as errs called it.
|
||||||
if (ty.kind === "var") {
|
if (ty.kind === "var") {
|
||||||
return this.tryResolveVar(ty.index) ?? ty;
|
return this.tryResolveVar(ty.index) ?? ty;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -582,7 +560,7 @@ export class InferContext {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "struct": {
|
case "struct": {
|
||||||
if (rhs.kind === "struct" && lhs.name === rhs.name) {
|
if (rhs.kind === "struct" && lhs.itemId === rhs.itemId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -595,52 +573,45 @@ export class InferContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkBody(
|
type FuncCtx = {
|
||||||
gcx: GlobalContext,
|
cx: TypeckCtx;
|
||||||
ast: Crate<Resolved>,
|
infcx: InferContext;
|
||||||
body: Expr<Resolved>,
|
localTys: Ty[];
|
||||||
fnTy: TyFn,
|
loopState: LoopState[];
|
||||||
typeOfItem: (itemId: ItemId, cause: Span) => Ty,
|
checkExpr: (expr: Expr<Resolved>) => Expr<Typecked>;
|
||||||
): Expr<Typecked> {
|
};
|
||||||
const localTys = [...fnTy.params];
|
|
||||||
const loopState: { hasBreak: boolean; loopId: LoopId }[] = [];
|
|
||||||
|
|
||||||
const infcx = new InferContext();
|
type LoopState = { hasBreak: boolean; loopId: LoopId };
|
||||||
|
|
||||||
function typeOf(res: Resolution, span: Span): Ty {
|
function typeOfValue(fcx: FuncCtx, res: Resolution, span: Span): Ty {
|
||||||
switch (res.kind) {
|
switch (res.kind) {
|
||||||
case "local": {
|
case "local": {
|
||||||
const idx = localTys.length - 1 - res.index;
|
const idx = fcx.localTys.length - 1 - res.index;
|
||||||
return localTys[idx];
|
return fcx.localTys[idx];
|
||||||
}
|
}
|
||||||
case "item": {
|
case "item": {
|
||||||
return typeOfItem(res.id, span);
|
return typeOfItem(fcx.cx, res.id, span);
|
||||||
}
|
}
|
||||||
case "builtin":
|
case "builtin":
|
||||||
return typeOfBuiltinValue(res.name, span);
|
return typeOfBuiltinValue(res.name, span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function lowerAstTy(type: Type<Resolved>): Ty {
|
export function checkBody(
|
||||||
return lowerAstTyBase(
|
cx: TypeckCtx,
|
||||||
type,
|
ast: Crate<Resolved>,
|
||||||
(ident) => {
|
body: Expr<Resolved>,
|
||||||
const res = ident.res;
|
fnTy: TyFn,
|
||||||
switch (res.kind) {
|
): Expr<Typecked> {
|
||||||
case "local": {
|
const infcx = new InferContext();
|
||||||
const idx = localTys.length - 1 - res.index;
|
|
||||||
return localTys[idx];
|
const fcx: FuncCtx = {
|
||||||
}
|
cx,
|
||||||
case "item": {
|
infcx,
|
||||||
return typeOfItem(res.id, type.span);
|
localTys: [...fnTy.params],
|
||||||
}
|
loopState: [],
|
||||||
case "builtin":
|
checkExpr: () => unreachable(),
|
||||||
return builtinAsTy(res.name, ident.span);
|
};
|
||||||
}
|
|
||||||
},
|
|
||||||
typeOfItem,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const checker: Folder<Resolved, Typecked> = {
|
const checker: Folder<Resolved, Typecked> = {
|
||||||
...mkDefaultFolder(),
|
...mkDefaultFolder(),
|
||||||
|
|
@ -650,7 +621,7 @@ export function checkBody(
|
||||||
return { ...expr, ty: TY_UNIT };
|
return { ...expr, ty: TY_UNIT };
|
||||||
}
|
}
|
||||||
case "let": {
|
case "let": {
|
||||||
const loweredBindingTy = expr.type && lowerAstTy(expr.type);
|
const loweredBindingTy = expr.type && lowerAstTy(cx, expr.type);
|
||||||
const bindingTy = loweredBindingTy
|
const bindingTy = loweredBindingTy
|
||||||
? loweredBindingTy
|
? loweredBindingTy
|
||||||
: infcx.newVar();
|
: infcx.newVar();
|
||||||
|
|
@ -660,7 +631,7 @@ export function checkBody(
|
||||||
|
|
||||||
// AST validation ensures that lets can only be in blocks, where
|
// AST validation ensures that lets can only be in blocks, where
|
||||||
// the types will be popped.
|
// the types will be popped.
|
||||||
localTys.push(bindingTy);
|
fcx.localTys.push(bindingTy);
|
||||||
|
|
||||||
expr.local!.ty = bindingTy;
|
expr.local!.ty = bindingTy;
|
||||||
|
|
||||||
|
|
@ -684,13 +655,14 @@ export function checkBody(
|
||||||
infcx.assign(lhs.ty, rhs.ty, expr.span);
|
infcx.assign(lhs.ty, rhs.ty, expr.span);
|
||||||
|
|
||||||
switch (lhs.kind) {
|
switch (lhs.kind) {
|
||||||
case "ident": {
|
case "ident":
|
||||||
const { res } = lhs.value;
|
case "path": {
|
||||||
|
const { res } = lhs.kind === "path" ? lhs : lhs.value;
|
||||||
switch (res.kind) {
|
switch (res.kind) {
|
||||||
case "local":
|
case "local":
|
||||||
break;
|
break;
|
||||||
case "item": {
|
case "item": {
|
||||||
const item = gcx.findItem(res.id, ast);
|
const item = cx.gcx.findItem(res.id, ast);
|
||||||
if (item.kind !== "global") {
|
if (item.kind !== "global") {
|
||||||
throw new CompilerError("cannot assign to item", expr.span);
|
throw new CompilerError("cannot assign to item", expr.span);
|
||||||
}
|
}
|
||||||
|
|
@ -721,13 +693,13 @@ export function checkBody(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "block": {
|
case "block": {
|
||||||
const prevLocalTysLen = localTys.length;
|
const prevLocalTysLen = fcx.localTys.length;
|
||||||
|
|
||||||
const exprs = expr.exprs.map((expr) => this.expr(expr));
|
const exprs = expr.exprs.map((expr) => this.expr(expr));
|
||||||
|
|
||||||
const ty = exprs.length > 0 ? exprs[exprs.length - 1].ty : TY_UNIT;
|
const ty = exprs.length > 0 ? exprs[exprs.length - 1].ty : TY_UNIT;
|
||||||
|
|
||||||
localTys.length = prevLocalTysLen;
|
fcx.localTys.length = prevLocalTysLen;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...expr,
|
...expr,
|
||||||
|
|
@ -758,22 +730,16 @@ export function checkBody(
|
||||||
return { ...expr, ty };
|
return { ...expr, ty };
|
||||||
}
|
}
|
||||||
case "ident": {
|
case "ident": {
|
||||||
const ty = typeOf(expr.value.res, expr.value.span);
|
const ty = typeOfValue(fcx, expr.value.res, expr.value.span);
|
||||||
|
|
||||||
return { ...expr, ty };
|
return { ...expr, ty };
|
||||||
}
|
}
|
||||||
case "path": {
|
case "path": {
|
||||||
const ty = typeOf(expr.res, expr.span);
|
const ty = typeOfValue(fcx, expr.res, expr.span);
|
||||||
return { ...expr, ty };
|
return { ...expr, ty };
|
||||||
}
|
}
|
||||||
case "binary": {
|
case "binary": {
|
||||||
const lhs = this.expr(expr.lhs);
|
return checkBinary(fcx, expr);
|
||||||
const rhs = this.expr(expr.rhs);
|
|
||||||
|
|
||||||
lhs.ty = infcx.resolveIfPossible(lhs.ty);
|
|
||||||
rhs.ty = infcx.resolveIfPossible(rhs.ty);
|
|
||||||
|
|
||||||
return checkBinary(expr, lhs, rhs);
|
|
||||||
}
|
}
|
||||||
case "unary": {
|
case "unary": {
|
||||||
const rhs = this.expr(expr.rhs);
|
const rhs = this.expr(expr.rhs);
|
||||||
|
|
@ -781,55 +747,7 @@ export function checkBody(
|
||||||
return checkUnary(expr, rhs);
|
return checkUnary(expr, rhs);
|
||||||
}
|
}
|
||||||
case "call": {
|
case "call": {
|
||||||
if (
|
return checkCall(fcx, expr);
|
||||||
expr.lhs.kind === "ident" &&
|
|
||||||
expr.lhs.value.res.kind === "builtin" &&
|
|
||||||
expr.lhs.value.res.name === "___transmute"
|
|
||||||
) {
|
|
||||||
const ty = infcx.newVar();
|
|
||||||
const args = expr.args.map((arg) => this.expr(arg));
|
|
||||||
const ret: Expr<Typecked> = {
|
|
||||||
...expr,
|
|
||||||
lhs: { ...expr.lhs, ty: TY_UNIT },
|
|
||||||
args,
|
|
||||||
ty,
|
|
||||||
};
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lhs = this.expr(expr.lhs);
|
|
||||||
lhs.ty = infcx.resolveIfPossible(lhs.ty);
|
|
||||||
const lhsTy = lhs.ty;
|
|
||||||
if (lhsTy.kind !== "fn") {
|
|
||||||
throw new CompilerError(
|
|
||||||
`expression of type ${printTy(lhsTy)} is not callable`,
|
|
||||||
lhs.span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = expr.args.map((arg) => this.expr(arg));
|
|
||||||
|
|
||||||
lhsTy.params.forEach((param, i) => {
|
|
||||||
if (args.length <= i) {
|
|
||||||
throw new CompilerError(
|
|
||||||
`missing argument of type ${printTy(param)}`,
|
|
||||||
expr.span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const arg = checker.expr(args[i]);
|
|
||||||
|
|
||||||
infcx.assign(param, arg.ty, args[i].span);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (args.length > lhsTy.params.length) {
|
|
||||||
throw new CompilerError(
|
|
||||||
`too many arguments passed, expected ${lhsTy.params.length}, found ${args.length}`,
|
|
||||||
expr.span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...expr, lhs, args, ty: lhsTy.returnTy };
|
|
||||||
}
|
}
|
||||||
case "fieldAccess": {
|
case "fieldAccess": {
|
||||||
const lhs = this.expr(expr.lhs);
|
const lhs = this.expr(expr.lhs);
|
||||||
|
|
@ -922,7 +840,7 @@ export function checkBody(
|
||||||
return { ...expr, cond, then, else: elsePart, ty };
|
return { ...expr, cond, then, else: elsePart, ty };
|
||||||
}
|
}
|
||||||
case "loop": {
|
case "loop": {
|
||||||
loopState.push({
|
fcx.loopState.push({
|
||||||
hasBreak: false,
|
hasBreak: false,
|
||||||
loopId: expr.loopId,
|
loopId: expr.loopId,
|
||||||
});
|
});
|
||||||
|
|
@ -930,7 +848,7 @@ export function checkBody(
|
||||||
const body = this.expr(expr.body);
|
const body = this.expr(expr.body);
|
||||||
infcx.assign(TY_UNIT, body.ty, body.span);
|
infcx.assign(TY_UNIT, body.ty, body.span);
|
||||||
|
|
||||||
const hadBreak = loopState.pop();
|
const hadBreak = fcx.loopState.pop();
|
||||||
const ty = hadBreak ? TY_UNIT : TY_NEVER;
|
const ty = hadBreak ? TY_UNIT : TY_NEVER;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -940,11 +858,12 @@ export function checkBody(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "break": {
|
case "break": {
|
||||||
if (loopState.length === 0) {
|
const loopStateLength = fcx.loopState.length;
|
||||||
|
if (loopStateLength === 0) {
|
||||||
throw new CompilerError("break outside loop", expr.span);
|
throw new CompilerError("break outside loop", expr.span);
|
||||||
}
|
}
|
||||||
const target = loopState[loopState.length - 1].loopId;
|
const target = fcx.loopState[loopStateLength - 1].loopId;
|
||||||
loopState[loopState.length - 1].hasBreak = true;
|
fcx.loopState[loopStateLength - 1].hasBreak = true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...expr,
|
...expr,
|
||||||
|
|
@ -957,7 +876,7 @@ export function checkBody(
|
||||||
({ name, expr }) => ({ name, expr: this.expr(expr) }),
|
({ name, expr }) => ({ name, expr: this.expr(expr) }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const structTy = typeOf(expr.name.res, expr.name.span);
|
const structTy = typeOfValue(fcx, expr.name.res, expr.name.span);
|
||||||
|
|
||||||
if (structTy.kind !== "struct") {
|
if (structTy.kind !== "struct") {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
|
|
@ -1022,52 +941,27 @@ export function checkBody(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fcx.checkExpr = checker.expr.bind(checker);
|
||||||
|
|
||||||
const checked = checker.expr(body);
|
const checked = checker.expr(body);
|
||||||
|
|
||||||
infcx.assign(fnTy.returnTy, checked.ty, body.span);
|
infcx.assign(fnTy.returnTy, checked.ty, body.span);
|
||||||
|
|
||||||
const resolveTy = (ty: Ty, span: Span) => {
|
const resolved = resolveBody(infcx, checked);
|
||||||
const resTy = infcx.resolveIfPossible(ty);
|
|
||||||
// TODO: When doing deep resolution, we need to check for _any_ vars.
|
|
||||||
if (resTy.kind === "var") {
|
|
||||||
throw new CompilerError("cannot infer type", span);
|
|
||||||
}
|
|
||||||
return resTy;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolver: Folder<Typecked, Typecked> = {
|
|
||||||
...mkDefaultFolder(),
|
|
||||||
expr(expr) {
|
|
||||||
const ty = resolveTy(expr.ty, expr.span);
|
|
||||||
|
|
||||||
if (expr.kind === "block") {
|
|
||||||
expr.locals!.forEach((local) => {
|
|
||||||
local.ty = resolveTy(local.ty!, local.span);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const innerExpr = superFoldExpr(expr, this);
|
|
||||||
|
|
||||||
return { ...innerExpr, ty };
|
|
||||||
},
|
|
||||||
type(type) {
|
|
||||||
return type;
|
|
||||||
},
|
|
||||||
ident(ident) {
|
|
||||||
return ident;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolved = resolver.expr(checked);
|
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkBinary(
|
function checkBinary(
|
||||||
|
fcx: FuncCtx,
|
||||||
expr: Expr<Resolved> & ExprBinary<Resolved>,
|
expr: Expr<Resolved> & ExprBinary<Resolved>,
|
||||||
lhs: Expr<Typecked>,
|
|
||||||
rhs: Expr<Typecked>,
|
|
||||||
): Expr<Typecked> {
|
): Expr<Typecked> {
|
||||||
|
const lhs = fcx.checkExpr(expr.lhs);
|
||||||
|
const rhs = fcx.checkExpr(expr.rhs);
|
||||||
|
|
||||||
|
lhs.ty = fcx.infcx.resolveIfPossible(lhs.ty);
|
||||||
|
rhs.ty = fcx.infcx.resolveIfPossible(rhs.ty);
|
||||||
|
|
||||||
const lhsTy = lhs.ty;
|
const lhsTy = lhs.ty;
|
||||||
const rhsTy = rhs.ty;
|
const rhsTy = rhs.ty;
|
||||||
|
|
||||||
|
|
@ -1134,3 +1028,98 @@ function checkUnary(
|
||||||
expr.span,
|
expr.span,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkCall(
|
||||||
|
fcx: FuncCtx,
|
||||||
|
expr: ExprCall<Resolved> & Expr<Resolved>,
|
||||||
|
): Expr<Typecked> {
|
||||||
|
if (
|
||||||
|
expr.lhs.kind === "ident" &&
|
||||||
|
expr.lhs.value.res.kind === "builtin" &&
|
||||||
|
expr.lhs.value.res.name === "___transmute"
|
||||||
|
) {
|
||||||
|
const ty = fcx.infcx.newVar();
|
||||||
|
const args = expr.args.map((arg) => fcx.checkExpr(arg));
|
||||||
|
const ret: Expr<Typecked> = {
|
||||||
|
...expr,
|
||||||
|
lhs: { ...expr.lhs, ty: TY_UNIT },
|
||||||
|
args,
|
||||||
|
ty,
|
||||||
|
};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lhs = fcx.checkExpr(expr.lhs);
|
||||||
|
lhs.ty = fcx.infcx.resolveIfPossible(lhs.ty);
|
||||||
|
const lhsTy = lhs.ty;
|
||||||
|
if (lhsTy.kind !== "fn") {
|
||||||
|
throw new CompilerError(
|
||||||
|
`expression of type ${printTy(lhsTy)} is not callable`,
|
||||||
|
lhs.span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = expr.args.map((arg) => fcx.checkExpr(arg));
|
||||||
|
|
||||||
|
lhsTy.params.forEach((param, i) => {
|
||||||
|
if (args.length <= i) {
|
||||||
|
throw new CompilerError(
|
||||||
|
`missing argument of type ${printTy(param)}`,
|
||||||
|
expr.span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fcx.infcx.assign(param, args[i].ty, args[i].span);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (args.length > lhsTy.params.length) {
|
||||||
|
throw new CompilerError(
|
||||||
|
`too many arguments passed, expected ${lhsTy.params.length}, found ${args.length}`,
|
||||||
|
expr.span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...expr, lhs, args, ty: lhsTy.returnTy };
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveBody(
|
||||||
|
infcx: InferContext,
|
||||||
|
checked: Expr<Typecked>,
|
||||||
|
): Expr<Typecked> {
|
||||||
|
const resolveTy = (ty: Ty, span: Span) => {
|
||||||
|
const resTy = infcx.resolveIfPossible(ty);
|
||||||
|
// TODO: When doing deep resolution, we need to check for _any_ vars.
|
||||||
|
if (resTy.kind === "var") {
|
||||||
|
throw new CompilerError("cannot infer type", span);
|
||||||
|
}
|
||||||
|
return resTy;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolver: Folder<Typecked, Typecked> = {
|
||||||
|
...mkDefaultFolder(),
|
||||||
|
expr(expr) {
|
||||||
|
const ty = resolveTy(expr.ty, expr.span);
|
||||||
|
|
||||||
|
if (expr.kind === "block") {
|
||||||
|
expr.locals!.forEach((local) => {
|
||||||
|
local.ty = resolveTy(local.ty!, local.span);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const innerExpr = superFoldExpr(expr, this);
|
||||||
|
|
||||||
|
return { ...innerExpr, ty };
|
||||||
|
},
|
||||||
|
type(type) {
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
ident(ident) {
|
||||||
|
return ident;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolved = resolver.expr(checked);
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
|
||||||
25
ui-tests/ui/item_tys.nil
Normal file
25
ui-tests/ui/item_tys.nil
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
//@check-pass
|
||||||
|
|
||||||
|
type A = struct {
|
||||||
|
b: B,
|
||||||
|
c: C,
|
||||||
|
};
|
||||||
|
|
||||||
|
type B = struct {};
|
||||||
|
|
||||||
|
type C = struct {
|
||||||
|
b: B,
|
||||||
|
};
|
||||||
|
|
||||||
|
function test(a: A): A = a;
|
||||||
|
|
||||||
|
function main() = (
|
||||||
|
let a = A {
|
||||||
|
b: B {},
|
||||||
|
c: C {
|
||||||
|
b: B {},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test(a);
|
||||||
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue