typeck refactors

This commit is contained in:
nora 2023-12-16 13:00:49 +01:00
parent b4fb837efe
commit dbd49d852f
3 changed files with 113 additions and 87 deletions

View file

@ -17,6 +17,7 @@ import {
Typecked, Typecked,
mkDefaultFolder, mkDefaultFolder,
superFoldExpr, superFoldExpr,
ExprStructLiteral,
} from "../ast"; } from "../ast";
import { CompilerError, ErrorEmitted, Span, unreachable } from "../error"; import { CompilerError, ErrorEmitted, Span, unreachable } from "../error";
import { printTy } from "../printer"; import { printTy } from "../printer";
@ -45,27 +46,6 @@ type FuncCtx = {
type LoopState = { hasBreak: boolean; loopId: LoopId }; type LoopState = { hasBreak: boolean; loopId: LoopId };
function typeOfValue(fcx: FuncCtx, res: Resolution, span: Span): Ty {
switch (res.kind) {
case "local": {
const idx = fcx.localTys.length - 1 - res.index;
return fcx.localTys[idx];
}
case "item": {
return typeOfItem(fcx.cx, res.id, [], span);
}
case "builtin":
return typeOfBuiltinValue(fcx, res.name, span);
case "tyParam":
return tyError(
fcx.cx,
new CompilerError(`type parameter cannot be used as value`, span),
);
case "error":
return tyErrorFrom(res);
}
}
export function typeOfBuiltinValue( export function typeOfBuiltinValue(
fcx: FuncCtx, fcx: FuncCtx,
name: BuiltinName, name: BuiltinName,
@ -257,7 +237,34 @@ export function checkBody(
} }
case "ident": case "ident":
case "path": { case "path": {
const ty = typeOfValue(fcx, expr.value.res, expr.value.span); const { res, span } = expr.value;
let ty: Ty;
switch (res.kind) {
case "local": {
const idx = fcx.localTys.length - 1 - res.index;
ty = fcx.localTys[idx];
break;
}
case "item": {
ty = typeOfItem(fcx.cx, res.id, [], span);
break;
}
case "builtin":
ty = typeOfBuiltinValue(fcx, res.name, span);
break;
case "tyParam":
ty = tyError(
fcx.cx,
new CompilerError(
`type parameter cannot be used as value`,
span,
),
);
break;
case "error":
ty = tyErrorFrom(res);
break;
}
return { ...expr, ty }; return { ...expr, ty };
} }
@ -437,61 +444,7 @@ export function checkBody(
}; };
} }
case "structLiteral": { case "structLiteral": {
const fields = expr.fields.map<StructLiteralField<Typecked>>( return checkStructLiteral(fcx, this, expr);
({ name, expr }) => ({ name, expr: this.expr(expr) }),
);
const structTy = typeOfValue(fcx, expr.name.res, expr.name.span);
if (structTy.kind !== "struct") {
const err: ErrorEmitted = emitError(
fcx.cx,
new CompilerError(
`struct literal is only allowed for struct types`,
expr.span,
),
);
return exprError(err, expr.span);
}
const assignedFields = new Set();
fields.forEach(({ name, expr: field }, i) => {
const fieldIdx = structTy.fields_no_subst.findIndex(
(def) => def[0] === name.name,
);
if (fieldIdx == -1) {
emitError(
fcx.cx,
new CompilerError(
`field ${name.name} doesn't exist on type ${expr.name.name}`,
name.span,
),
);
}
const fieldTy = structTy.fields_no_subst[fieldIdx];
infcx.assign(fieldTy[1], field.ty, field.span);
assignedFields.add(name.name);
fields[i].fieldIdx = fieldIdx;
});
const missing: string[] = [];
structTy.fields_no_subst.forEach(([name]) => {
if (!assignedFields.has(name)) {
missing.push(name);
}
});
if (missing.length > 0) {
emitError(
fcx.cx,
new CompilerError(
`missing fields in literal: ${missing.join(", ")}`,
expr.span,
),
);
}
return { ...expr, fields, ty: structTy };
} }
case "tupleLiteral": { case "tupleLiteral": {
const fields = expr.fields.map((expr) => this.expr(expr)); const fields = expr.fields.map((expr) => this.expr(expr));
@ -617,6 +570,81 @@ function checkInlineAsm(
return { kind: "asm", locals, ty: retTy, instructions, span: body.span }; return { kind: "asm", locals, ty: retTy, instructions, span: body.span };
} }
function checkStructLiteral(
fcx: FuncCtx,
self: Folder<Resolved, Typecked>,
expr: ExprStructLiteral<Resolved> & Expr<Resolved>,
) {
const fields = expr.fields.map<StructLiteralField<Typecked>>(
({ name, expr }) => ({ name, expr: self.expr(expr) }),
);
const { name } = expr;
if (name.res.kind !== "item") {
return exprError(
emitError(
fcx.cx,
new CompilerError("struct literal must be struct type", name.span),
),
name.span,
);
}
// TODO: Handle generic arugments
const structTy = typeOfItem(fcx.cx, name.res.id, [], name.span);
if (structTy.kind !== "struct") {
const err: ErrorEmitted = emitError(
fcx.cx,
new CompilerError(
`struct literal is only allowed for struct types`,
expr.span,
),
);
return exprError(err, expr.span);
}
const assignedFields = new Set();
fields.forEach(({ name, expr: field }, i) => {
const fieldIdx = structTy.fields_no_subst.findIndex(
(def) => def[0] === name.name,
);
if (fieldIdx == -1) {
emitError(
fcx.cx,
new CompilerError(
`field ${name.name} doesn't exist on type ${name.name}`,
name.span,
),
);
}
const fieldTy = structTy.fields_no_subst[fieldIdx];
fcx.infcx.assign(fieldTy[1], field.ty, field.span);
assignedFields.add(name.name);
fields[i].fieldIdx = fieldIdx;
});
const missing: string[] = [];
structTy.fields_no_subst.forEach(([name]) => {
if (!assignedFields.has(name)) {
missing.push(name);
}
});
if (missing.length > 0) {
emitError(
fcx.cx,
new CompilerError(
`missing fields in literal: ${missing.join(", ")}`,
expr.span,
),
);
}
return { ...expr, fields, ty: structTy };
}
function checkLValue(cx: TypeckCtx, expr: Expr<Typecked>) { function checkLValue(cx: TypeckCtx, expr: Expr<Typecked>) {
switch (expr.kind) { switch (expr.kind) {
case "ident": case "ident":

View file

@ -28,8 +28,9 @@ function builtinAsTy(cx: TypeckCtx, name: string, span: Span): Ty {
* Lowers the AST representation of a type into its resolved Ty representation. * Lowers the AST representation of a type into its resolved Ty representation.
* Will also validate the type, for example ensuring that generic arguments match up. * Will also validate the type, for example ensuring that generic arguments match up.
*/ */
// TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused.
export function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty { export function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty {
// This function is called for every syntactical type in the program.
// Could be a function argument, but also a struct field or a local variable annotation.
switch (type.kind) { switch (type.kind) {
case "ident": { case "ident": {
const ident = type.value; const ident = type.value;

View file

@ -1,11 +1,8 @@
function main() = ; function main() = ;
function memcpy(dst: I32, src: I32, n: I32) = type A[T] = T;
___asm( type B[T] = struct { a: T };
__locals(),
"local.get 2", //function ohno(x: A[A[A[I32]]]): I32 = x;
"local.get 1",
"local.get 0", function generic(a: B[I32]): B[I32] = a;
"memory.copy",
)
;