typeck cleanup

This commit is contained in:
nora 2023-08-03 14:40:25 +02:00
parent 73a369730b
commit ba3a199249
9 changed files with 345 additions and 319 deletions

View file

@ -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",

View file

@ -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][];
}; };

View file

@ -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]],
}; };

View file

@ -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": {

View file

@ -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}` : ""}`,
);
}

View file

@ -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) =;
`; `;

View file

@ -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 "!";

View file

@ -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
View 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);
);