diff --git a/src/ast.ts b/src/ast.ts index 3120960..1362d17 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -538,7 +538,7 @@ export type TyStruct = { kind: "struct"; itemId: ItemId; params: string[]; - args: Ty[]; + genericArgs: Ty[]; _name: string; fields_no_subst: [string, Ty][]; }; @@ -563,6 +563,13 @@ export type TyParam = { name: string; }; +export type TyAlias = { + kind: "alias"; + actual: Ty; + genericArgs: Ty[]; + params: string[]; +}; + export type TyError = { kind: "error"; err: ErrorEmitted; @@ -580,6 +587,7 @@ export type Ty = | TyRawPtr | TyNever | TyParam + | TyAlias | TyError; export function tyIsUnit(ty: Ty): ty is TyUnit { @@ -598,7 +606,7 @@ export type TypeckResults = { }; export function structFieldsSubstituted(ty: TyStruct): [string, Ty][] { - const args = ty.args; + const args = ty.genericArgs; return ty.fields_no_subst.map(([name, type]) => [ name, substituteTy(args, type), @@ -607,12 +615,16 @@ export function structFieldsSubstituted(ty: TyStruct): [string, Ty][] { // Substitute the parameter of a type. We are only able to handle one // level of generic definitions, for example for fields the struct def or for exprs the function generics. -export function substituteTy(params: Ty[], ty: Ty): Ty { - const subst = (ty: Ty) => substituteTy(params, ty); - +export function substituteTy(genericArgs: Ty[], ty: Ty): Ty { + const subst = (ty: Ty) => substituteTy(genericArgs, ty); switch (ty.kind) { case "param": - return params[ty.idx]; + if (ty.idx >= genericArgs.length) { + throw new Error( + `substitution out of range, param index ${ty.idx} of param ${ty.name} out of range for length ${genericArgs.length}`, + ); + } + return genericArgs[ty.idx]; case "tuple": return { ...ty, elems: ty.elems.map(subst) }; case "fn": @@ -622,9 +634,10 @@ export function substituteTy(params: Ty[], ty: Ty): Ty { params: ty.params.map(subst), }; case "struct": + case "alias": return { ...ty, - args: ty.args.map(subst), + genericArgs: ty.genericArgs.map(subst), }; case "rawptr": return { ...ty, inner: subst(ty.inner) }; @@ -952,6 +965,6 @@ export function varUnreachable(): never { unreachable("Type variables must not occur after type checking"); } -export function paramUnreachable(): never { - unreachable("Type parameters must not occur after monomophization"); +export function paramUnreachable(ty: Ty): never { + unreachable(`type ${ty.kind} should never occur in codegen`); } diff --git a/src/codegen.test.ts b/src/codegen.test.ts index 3859ef3..e71e1c4 100644 --- a/src/codegen.test.ts +++ b/src/codegen.test.ts @@ -5,7 +5,7 @@ it("should compute struct layout correctly", () => { const ty: TyStruct = { kind: "struct", itemId: ItemId.dummy(), - args: [], + genericArgs: [], params: [], _name: "", fields_no_subst: [ @@ -52,7 +52,7 @@ it("should compute single field struct layout correctly", () => { const ty: TyStruct = { kind: "struct", itemId: ItemId.dummy(), - args: [], + genericArgs: [], params: [], _name: "", fields_no_subst: [["owo", TY_INT]], diff --git a/src/codegen.ts b/src/codegen.ts index b6d5879..ce82514 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -20,7 +20,7 @@ import { superFoldItem, varUnreachable, TyRawPtr, - paramUnreachable, + paramUnreachable as codegenUnreachableTy, structFieldsSubstituted, } from "./ast"; import { GlobalContext } from "./context"; @@ -1256,11 +1256,10 @@ function argRetAbi(param: Ty): ArgRetAbi { case "never": return []; case "var": - varUnreachable(); case "param": - paramUnreachable(); + case "alias": case "error": - unreachable("codegen should not see errors"); + codegenUnreachableTy(param); } } @@ -1313,11 +1312,10 @@ function wasmTypeForBody(ty: Ty): wasm.ValType[] { case "never": return []; case "var": - varUnreachable(); case "param": - paramUnreachable(); + case "alias": case "error": - unreachable("codegen should not see errors"); + codegenUnreachableTy(ty); } } diff --git a/src/printer.ts b/src/printer.ts index e6a4f0c..98aa0b0 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -13,6 +13,7 @@ import { Type, ItemType, tyIsUnit, + substituteTy, } from "./ast"; export function printAst(ast: Crate): string { @@ -255,40 +256,32 @@ function printIdent(ident: IdentWithRes): string { export function printTy(ty: Ty): string { switch (ty.kind) { - case "string": { + case "string": return "String"; - } - case "int": { + case "int": return "Int"; - } - case "i32": { + case "i32": return "I32"; - } - case "bool": { + case "bool": return "Bool"; - } - case "tuple": { + case "tuple": return `(${ty.elems.map(printTy).join(", ")})`; - } case "fn": { const ret = tyIsUnit(ty.returnTy) ? "" : `: ${printTy(ty.returnTy)}`; return `fn(${ty.params.map(printTy).join(", ")})${ret}`; } - case "var": { + case "var": return `?${ty.index}`; - } - case "struct": { + case "struct": return ty._name; - } - case "rawptr": { + case "rawptr": return `*${printTy(ty.inner)}`; - } - case "never": { + case "never": return "!"; - } - case "param": { + case "param": return ty.name; - } + case "alias": + return printTy(substituteTy(ty.genericArgs, ty.actual)); case "error": return ""; } diff --git a/src/typeck.ts b/src/typeck.ts index 9130bb4..98d9e77 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -29,6 +29,7 @@ import { StructLiteralField, superFoldExpr, ExprCall, + substituteTy, } from "./ast"; import { GlobalContext } from "./context"; import { @@ -140,7 +141,7 @@ function lowerAstTy(cx: TypeckCtx, type: Type): Ty { throw new Error("Item type cannot refer to local variable"); } case "item": { - ty = typeOfItem(cx, res.id, type.span); + ty = typeOfItem(cx, res.id, generics, type.span); break; } case "builtin": { @@ -157,9 +158,12 @@ function lowerAstTy(cx: TypeckCtx, type: Type): Ty { } } - if (ty.kind === "struct") { + if (ty.kind === "struct" || ty.kind === "alias") { if (generics.length === ty.params.length) { - return { ...ty, args: generics }; + if (ty.kind === "alias") { + return substituteTy(ty.genericArgs, ty.actual); + } + return { ...ty, genericArgs: generics }; } else { return tyError( cx, @@ -209,7 +213,12 @@ function lowerAstTy(cx: TypeckCtx, type: Type): Ty { } } -function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty { +function typeOfItem( + cx: TypeckCtx, + itemId: ItemId, + genericArgs: Ty[], + cause: Span, +): Ty { if (itemId.crateId !== cx.ast.id) { // Look up foreign items in the foreign crates, we don't need to lower those // ourselves. @@ -220,7 +229,7 @@ function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty { case "import": case "type": case "global": - return item.ty!; + return substituteTy(genericArgs, item.ty!); case "mod": { return tyError( cx, @@ -276,7 +285,13 @@ function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty { case "struct": { ty = { kind: "struct", - args: [], + genericArgs: item.generics.map( + ({ name }, idx): Ty => ({ + kind: "param", + name, + idx, + }), + ), params: item.generics.map((ident) => ident.name), itemId: item.id, _name: item.name, @@ -295,8 +310,20 @@ function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty { break; } case "alias": { - // TODO: subst - ty = lowerAstTy(cx, item.type.type); + const actual = lowerAstTy(cx, item.type.type); + + ty = { + kind: "alias", + actual, + genericArgs: item.generics.map( + ({ name }, idx): Ty => ({ + kind: "param", + name, + idx, + }), + ), + params: item.generics.map((ident) => ident.name), + }; break; } } @@ -329,6 +356,8 @@ function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty { } } + ty = substituteTy(genericArgs, ty); + cx.itemTys.set(item.id, ty); return ty; } @@ -348,7 +377,8 @@ export function typeck( itemInner(item: Item): Item { switch (item.kind) { case "function": { - const fnTy = typeOfItem(cx, item.id, item.span) as TyFn; + // Functions do not have generic arguments right now. + const fnTy = typeOfItem(cx, item.id, [], item.span) as TyFn; const body = checkBody(cx, ast, item.body, fnTy); return { @@ -360,7 +390,7 @@ export function typeck( }; } case "import": { - const fnTy = typeOfItem(cx, item.id, item.span) as TyFn; + const fnTy = typeOfItem(cx, item.id, [], item.span) as TyFn; fnTy.params.forEach((param, i) => { switch (param.kind) { @@ -404,7 +434,7 @@ export function typeck( }; } case "type": { - const ty = typeOfItem(cx, item.id, item.span); + const ty = typeOfItem(cx, item.id, [], item.span); switch (item.type.kind) { case "struct": { @@ -452,7 +482,7 @@ export function typeck( return item; } case "global": { - const ty = typeOfItem(cx, item.id, item.span); + const ty = typeOfItem(cx, item.id, [], item.span); const { init } = item; let initChecked: Expr; @@ -720,7 +750,7 @@ function typeOfValue(fcx: FuncCtx, res: Resolution, span: Span): Ty { return fcx.localTys[idx]; } case "item": { - return typeOfItem(fcx.cx, res.id, span); + return typeOfItem(fcx.cx, res.id, [], span); } case "builtin": return typeOfBuiltinValue(fcx, res.name, span); diff --git a/std/list.nil b/std/list.nil index d7e0184..b404871 100644 --- a/std/list.nil +++ b/std/list.nil @@ -1,21 +1,21 @@ -type List[T] = struct { - ptr: Int, - len: Int, - cap: Int, -}; - -function new(): List[Int] = ( - List { ptr: 0, len: 0, cap: 0 } -); - -function push(list: List[Int], elem: Int) = ( - growIfNeeded(list, 1); -); - +//type List[T] = struct { +// ptr: Int, +// len: Int, +// cap: Int, +//}; +// +//function new(): List[Int] = ( +// List { ptr: 0, len: 0, cap: 0 } +//); +// +//function push(list: List[Int], elem: Int) = ( +// growIfNeeded(list, 1); +//); +// // PRIVATE: - -function growIfNeeded(list: List[Int], elems: Int) = ( - if (list.len + elems) < list.cap then ( - let newMemory = std.rt.alloc.allocateItem(0_I32, 0_I32); - ); -); +// +//function growIfNeeded(list: List[Int], elems: Int) = ( +// if (list.len + elems) < list.cap then ( +// let newMemory = std.rt.alloc.allocateItem(0_I32, 0_I32); +// ); +//); diff --git a/test.nil b/test.nil index 60472c9..a25666c 100644 --- a/test.nil +++ b/test.nil @@ -1,6 +1,6 @@ //@check-pass -type A[T] = struct { a: T }; +type A[T] = T; function main() = ; -function test(a: A[I32]) = ; +function aaaaaaaaaaa(a: A[I32]) = ;