From 66d95dfeeba93d4c178326d9fe1d6cf2d35e8875 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sat, 16 Dec 2023 13:45:09 +0100 Subject: [PATCH] fix generics --- src/ast.ts | 6 +- src/codegen.test.ts | 2 - src/codegen.ts | 5 +- src/parser.ts | 4 +- src/printer.ts | 6 +- src/resolve.ts | 2 +- src/typeck/expr.ts | 7 +- src/typeck/index.ts | 8 +- src/typeck/item.ts | 150 ++++++++++-------- src/types.ts | 22 +-- test.nil | 12 +- ui-tests/type/generics/aliases_nested.nil | 6 + .../generics/generics_on_primitive.stderr | 2 +- .../generics/generics_structs_in_args.stderr | 17 -- ui-tests/type/generics/structs.nil | 3 + ui-tests/type/generics/structs.stderr | 17 -- ui-tests/type/generics/wrong_amount.nil | 2 + ui-tests/type/generics/wrong_amount.stderr | 41 +++-- 18 files changed, 154 insertions(+), 158 deletions(-) create mode 100644 ui-tests/type/generics/aliases_nested.nil delete mode 100644 ui-tests/type/generics/generics_structs_in_args.stderr delete mode 100644 ui-tests/type/generics/structs.stderr diff --git a/src/ast.ts b/src/ast.ts index 4b38e5d..2ba7f51 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -141,7 +141,7 @@ export type FunctionArg

= { export type ItemKindType

= { kind: "type"; - generics: Ident[]; + genericParams: Ident[]; type: TypeDefKind

; ty?: Ty; }; @@ -417,7 +417,7 @@ export const UNARY_KINDS: UnaryKind[] = ["!", "-"]; export type TypeKind

= | { kind: "ident"; - generics: Type

[]; + genericArgs: Type

[]; value: IdentWithRes

; } | { @@ -793,7 +793,7 @@ export function superFoldType( case "ident": { return { kind: "ident", - generics: type.generics.map((type) => folder.type(type)), + genericArgs: type.genericArgs.map((type) => folder.type(type)), value: folder.ident(type.value), span, }; diff --git a/src/codegen.test.ts b/src/codegen.test.ts index 25a4b67..27b73c4 100644 --- a/src/codegen.test.ts +++ b/src/codegen.test.ts @@ -7,7 +7,6 @@ it("should compute struct layout correctly", () => { kind: "struct", itemId: ItemId.dummy(), genericArgs: [], - params: [], _name: "", fields_no_subst: [ ["uwu", TYS.I32], @@ -54,7 +53,6 @@ it("should compute single field struct layout correctly", () => { kind: "struct", itemId: ItemId.dummy(), genericArgs: [], - params: [], _name: "", fields_no_subst: [["owo", TYS.INT]], }; diff --git a/src/codegen.ts b/src/codegen.ts index 06f0dd5..9880f66 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -384,8 +384,7 @@ function lowerFunc(cx: Context, func: ItemFunction) { fcx.wasm.body = body.instructions; } else { lowerExpr(fcx, wasmFunc.body, body); - - paramLocations.forEach((local) => { + paramLocations.forEach((local) => { const refcount = needsRefcount(local.ty); if (refcount !== undefined) { // TODO: correctly deal with tuples @@ -1264,7 +1263,6 @@ function argRetAbi(param: Ty): ArgRetAbi { return []; case "var": case "param": - case "alias": case "error": codegenUnreachableTy(param); } @@ -1320,7 +1318,6 @@ function wasmTypeForBody(ty: Ty): wasm.ValType[] { return []; case "var": case "param": - case "alias": case "error": codegenUnreachableTy(ty); } diff --git a/src/parser.ts b/src/parser.ts index a283e1d..6786c04 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -173,7 +173,7 @@ function parseItem(t: State): [State, Item] { { kind: "type", name: name.ident, - generics, + genericParams: generics, type, span: name.span, id: ItemId.dummy(), @@ -694,7 +694,7 @@ function parseType(t: State): [State, Type] { t, { kind: "ident", - generics, + genericArgs: generics, value: { name: tok.ident, span }, span, }, diff --git a/src/printer.ts b/src/printer.ts index ca7534f..977d578 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -65,9 +65,9 @@ function printFunction(func: ItemFunction): string { function printTypeDef(type: ItemType): string { const head = `type ${type.name}${ - type.generics.length === 0 + type.genericParams.length === 0 ? "" - : `[${type.generics.map((ident) => ident.name).join(", ")}]` + : `[${type.genericParams.map((ident) => ident.name).join(", ")}]` } = `; switch (type.type.kind) { case "struct": { @@ -284,8 +284,6 @@ export function printTy(ty: Ty): string { return "!"; case "param": return ty.name; - case "alias": - return printTy(substituteTy(ty.genericArgs, ty.actual)); case "error": return ""; } diff --git a/src/resolve.ts b/src/resolve.ts index c4e6bff..2cedad3 100644 --- a/src/resolve.ts +++ b/src/resolve.ts @@ -225,7 +225,7 @@ function resolveModule( }; } case "type": { - tyParamScopes = item.generics.map(({ name }) => name); + tyParamScopes = item.genericParams.map(({ name }) => name); const type = { ...superFoldItem(item, this) }; diff --git a/src/typeck/expr.ts b/src/typeck/expr.ts index 59cd660..2c9956e 100644 --- a/src/typeck/expr.ts +++ b/src/typeck/expr.ts @@ -10,7 +10,6 @@ import { Folder, LOGICAL_KINDS, LoopId, - Resolution, Resolved, StructLiteralField, Type, @@ -91,7 +90,6 @@ export function checkBody( fnTy: TyFn, ): Expr { const infcx = new InferContext(cx.gcx.error); - const fcx: FuncCtx = { cx, infcx, @@ -246,7 +244,8 @@ export function checkBody( break; } case "item": { - ty = typeOfItem(fcx.cx, res.id, [], span); + // TODO: what do we do about generis here? + ty = typeOfItem(fcx.cx, res.id, span); break; } case "builtin": @@ -592,7 +591,7 @@ function checkStructLiteral( } // TODO: Handle generic arugments - const structTy = typeOfItem(fcx.cx, name.res.id, [], name.span); + const structTy = typeOfItem(fcx.cx, name.res.id, name.span); if (structTy.kind !== "struct") { const err: ErrorEmitted = emitError( diff --git a/src/typeck/index.ts b/src/typeck/index.ts index a216eb2..b247b18 100644 --- a/src/typeck/index.ts +++ b/src/typeck/index.ts @@ -31,7 +31,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg): Pkg { switch (item.kind) { case "function": { // Functions do not have generic arguments right now. - const fnTy = typeOfItem(cx, item.id, [], item.span) as TyFn; + const fnTy = typeOfItem(cx, item.id, item.span) as TyFn; const body = checkBody(cx, ast, item.body, fnTy); return { @@ -43,7 +43,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg): Pkg { }; } 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) { @@ -87,7 +87,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg): Pkg { }; } case "type": { - const ty = typeOfItem(cx, item.id, [], item.span); + const ty = typeOfItem(cx, item.id, item.span); switch (item.type.kind) { case "struct": { @@ -135,7 +135,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg): Pkg { 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; diff --git a/src/typeck/item.ts b/src/typeck/item.ts index b084d28..576afbc 100644 --- a/src/typeck/item.ts +++ b/src/typeck/item.ts @@ -1,7 +1,7 @@ -import { ItemId, Resolved, Type } from "../ast"; +import { Ident, Item, ItemId, Resolved, Type, Typecked } from "../ast"; import { CompilerError, Span } from "../error"; import { printTy } from "../printer"; -import { TYS, Ty, substituteTy } from "../types"; +import { TYS, Ty, createIdentityGenericArgs, substituteTy } from "../types"; import { TypeckCtx, tyError, tyErrorFrom } from "./base"; function builtinAsTy(cx: TypeckCtx, name: string, span: Span): Ty { @@ -36,58 +36,77 @@ export function lowerAstTy(cx: TypeckCtx, type: Type): Ty { const ident = type.value; const res = ident.res; - const generics = type.generics.map((type) => lowerAstTy(cx, type)); + const genericArgs = type.genericArgs.map((type) => lowerAstTy(cx, type)); let ty: Ty; + let generics: Generics; + // We only actually substitute anything when "peeking" behind a type into its + // internals, where the params are used. This is only the case for aliases today. + let isAlias = false; switch (res.kind) { case "local": { throw new Error("Item type cannot refer to local variable"); } case "item": { - ty = typeOfItem(cx, res.id, generics, type.span); + ty = typeOfItem(cx, res.id, type.span); + const item = cx.gcx.findItem(res.id, cx.ast); + if (item.kind === "type" && item.type.kind === "alias") { + isAlias = true; + } + generics = itemGenerics(item); break; } case "builtin": { ty = builtinAsTy(cx, res.name, ident.span); + generics = { kind: "none" }; break; } case "tyParam": { ty = { kind: "param", idx: res.index, name: res.name }; + generics = { kind: "none" }; break; } case "error": { - ty = tyErrorFrom(res); - break; + // Skip generics validation, it's fine! + return tyErrorFrom(res); } } - if (ty.kind === "struct" || ty.kind === "alias") { - if (generics.length === ty.params.length) { - if (ty.kind === "alias") { - return substituteTy(ty.genericArgs, ty.actual); - } - return { ...ty, genericArgs: generics }; - } else { - return tyError( - cx, - new CompilerError( - `expected ${ty.params.length} generic arguments, found ${generics.length}`, - type.span, - ), - ); - } - } else if (ty.kind !== "error") { - if (generics.length > 0) { - return tyError( - cx, - new CompilerError( - `type ${printTy(ty)} does not take generic arguments`, - type.span, - ), - ); - } + if ( + (generics.kind === "none" || generics.params.length === 0) && + genericArgs.length > 0 + ) { + return tyError( + cx, + new CompilerError( + `type ${printTy(ty)} does not take any generic arguments but ${ + genericArgs.length + } were passed`, + type.span, + ), + ); + } + if ( + generics.kind === "some" && + generics.params.length > genericArgs.length + ) { + return tyError( + cx, + new CompilerError( + `missing generics for type ${printTy(ty)}, expected ${ + generics.params.length + }, but only ${genericArgs.length} were passed`, + type.span, + ), + ); + } + if (isAlias) { + return substituteTy(genericArgs, ty); + } else { + if (ty.kind === "struct") { + return { ...ty, genericArgs }; + } + return ty; } - - return ty; } case "tuple": { return { @@ -115,12 +134,31 @@ export function lowerAstTy(cx: TypeckCtx, type: Type): Ty { } } -export function typeOfItem( - cx: TypeckCtx, - itemId: ItemId, - genericArgs: Ty[], - cause: Span, -): Ty { +type Generics = + | { + kind: "none"; + } + | { + kind: "some"; + params: Ident[]; + }; + +function itemGenerics(item: Item | Item): Generics { + const none: Generics = { kind: "none" }; + switch (item.kind) { + case "function": + case "extern": + case "error": + case "global": + case "mod": + case "import": + return none; + case "type": + return { kind: "some", params: item.genericParams }; + } +} + +export function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty { if (itemId.pkgId !== cx.ast.id) { // Look up foreign items in the foreign pkgs, we don't need to lower those // ourselves. @@ -131,7 +169,7 @@ export function typeOfItem( case "import": case "type": case "global": - return substituteTy(genericArgs, item.ty!); + return item.ty!; case "mod": { return tyError( cx, @@ -187,14 +225,7 @@ export function typeOfItem( case "struct": { ty = { kind: "struct", - genericArgs: item.generics.map( - ({ name }, idx): Ty => ({ - kind: "param", - name, - idx, - }), - ), - params: item.generics.map((ident) => ident.name), + genericArgs: createIdentityGenericArgs(item.genericParams), itemId: item.id, _name: item.name, fields_no_subst: [ @@ -214,52 +245,41 @@ export function typeOfItem( case "alias": { 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), - }; + ty = actual; break; } } break; } case "mod": { - return tyError( + ty = tyError( cx, new CompilerError( `module ${item.name} cannot be used as a type or value`, cause, ), ); + break; } case "extern": { - return tyError( + ty = tyError( cx, new CompilerError( `extern declaration ${item.name} cannot be used as a type or value`, cause, ), ); + break; } case "global": { ty = lowerAstTy(cx, item.type); break; } case "error": { - return tyErrorFrom(item); + ty = tyErrorFrom(item); } } - ty = substituteTy(genericArgs, ty); - cx.itemTys.set(item.id, ty); return ty; } diff --git a/src/types.ts b/src/types.ts index c5bc28c..cb302d1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { ItemId, Resolution } from "./ast"; +import { Ident, ItemId, Resolution } from "./ast"; import { ErrorEmitted } from "./error"; export type TyString = { @@ -41,7 +41,6 @@ export type TyVar = { export type TyStruct = { kind: "struct"; itemId: ItemId; - params: string[]; genericArgs: Ty[]; _name: string; fields_no_subst: [string, Ty][]; @@ -67,13 +66,6 @@ export type TyParam = { name: string; }; -export type TyAlias = { - kind: "alias"; - actual: Ty; - genericArgs: Ty[]; - params: string[]; -}; - export type TyError = { kind: "error"; err: ErrorEmitted; @@ -91,7 +83,6 @@ export type Ty = | TyRawPtr | TyNever | TyParam - | TyAlias | TyError; export function tyIsUnit(ty: Ty): ty is TyUnit { @@ -140,7 +131,6 @@ export function substituteTy(genericArgs: Ty[], ty: Ty): Ty { params: ty.params.map(subst), }; case "struct": - case "alias": return { ...ty, genericArgs: ty.genericArgs.map(subst), @@ -158,3 +148,13 @@ export function substituteTy(genericArgs: Ty[], ty: Ty): Ty { return ty; } } + +export function createIdentityGenericArgs(params: Ident[]): Ty[] { + return params.map( + (name, idx): Ty => ({ + kind: "param", + name: name.name, + idx, + }), + ); +} diff --git a/test.nil b/test.nil index 1a3619e..53be386 100644 --- a/test.nil +++ b/test.nil @@ -1,8 +1,8 @@ +type A[T] = struct { a: T }; +type B[T] = struct { + b: T, +}; + function main() = ; -type A[T] = T; -type B[T] = struct { a: T }; - -//function ohno(x: A[A[A[I32]]]): I32 = x; - -function generic(a: B[I32]): B[I32] = a; +function test(b: B[I32]) = ; diff --git a/ui-tests/type/generics/aliases_nested.nil b/ui-tests/type/generics/aliases_nested.nil new file mode 100644 index 0000000..c88b744 --- /dev/null +++ b/ui-tests/type/generics/aliases_nested.nil @@ -0,0 +1,6 @@ +//@check-pass +type A[T] = T; + +function ohno(x: A[A[A[I32]]]): I32 = x; + +function main() = ; diff --git a/ui-tests/type/generics/generics_on_primitive.stderr b/ui-tests/type/generics/generics_on_primitive.stderr index 417f8c8..d4019c2 100644 --- a/ui-tests/type/generics/generics_on_primitive.stderr +++ b/ui-tests/type/generics/generics_on_primitive.stderr @@ -1,4 +1,4 @@ -error: type I32 does not take generic arguments +error: type I32 does not take any generic arguments but 1 were passed --> $DIR/generics_on_primitive.nil:2 2 | let a: I32[I32] = 0; ^^^ diff --git a/ui-tests/type/generics/generics_structs_in_args.stderr b/ui-tests/type/generics/generics_structs_in_args.stderr deleted file mode 100644 index a668fbc..0000000 --- a/ui-tests/type/generics/generics_structs_in_args.stderr +++ /dev/null @@ -1,17 +0,0 @@ -/home/nils/projects/riverdelta/target/ast.js:110 - throw new Error(`substitution out of range, param index ${ty.idx} of param ${ty.name} out of range for length ${genericArgs.length}`); - ^ - -Error: substitution out of range, param index 0 of param T out of range for length 0 - at substituteTy (/home/nils/projects/riverdelta/target/ast.js:110:23) - at subst (/home/nils/projects/riverdelta/target/ast.js:106:27) - at Array.map () - at substituteTy (/home/nils/projects/riverdelta/target/ast.js:125:45) - at typeOfItem (/home/nils/projects/riverdelta/target/typeck/item.js:193:33) - at Object.itemInner (/home/nils/projects/riverdelta/target/typeck/index.js:63:54) - at Object.item (/home/nils/projects/riverdelta/target/ast.js:146:34) - at /home/nils/projects/riverdelta/target/ast.js:164:55 - at Array.map () - at foldAst (/home/nils/projects/riverdelta/target/ast.js:164:34) - -Node.js v20.10.0 diff --git a/ui-tests/type/generics/structs.nil b/ui-tests/type/generics/structs.nil index 98079f2..274c1f9 100644 --- a/ui-tests/type/generics/structs.nil +++ b/ui-tests/type/generics/structs.nil @@ -1,3 +1,4 @@ +//@check-pass type A[T] = struct { a: T }; type B[T, U, V] = struct { b: T, @@ -6,3 +7,5 @@ type B[T, U, V] = struct { type C = (); function test(a: A[I32], b: B[I32, Int, I32], c: C) = ; + +function main() = ; diff --git a/ui-tests/type/generics/structs.stderr b/ui-tests/type/generics/structs.stderr deleted file mode 100644 index a668fbc..0000000 --- a/ui-tests/type/generics/structs.stderr +++ /dev/null @@ -1,17 +0,0 @@ -/home/nils/projects/riverdelta/target/ast.js:110 - throw new Error(`substitution out of range, param index ${ty.idx} of param ${ty.name} out of range for length ${genericArgs.length}`); - ^ - -Error: substitution out of range, param index 0 of param T out of range for length 0 - at substituteTy (/home/nils/projects/riverdelta/target/ast.js:110:23) - at subst (/home/nils/projects/riverdelta/target/ast.js:106:27) - at Array.map () - at substituteTy (/home/nils/projects/riverdelta/target/ast.js:125:45) - at typeOfItem (/home/nils/projects/riverdelta/target/typeck/item.js:193:33) - at Object.itemInner (/home/nils/projects/riverdelta/target/typeck/index.js:63:54) - at Object.item (/home/nils/projects/riverdelta/target/ast.js:146:34) - at /home/nils/projects/riverdelta/target/ast.js:164:55 - at Array.map () - at foldAst (/home/nils/projects/riverdelta/target/ast.js:164:34) - -Node.js v20.10.0 diff --git a/ui-tests/type/generics/wrong_amount.nil b/ui-tests/type/generics/wrong_amount.nil index 2987c69..3a58db2 100644 --- a/ui-tests/type/generics/wrong_amount.nil +++ b/ui-tests/type/generics/wrong_amount.nil @@ -21,3 +21,5 @@ function test( c2: C[], c3: C[I32], ) = ; + +function main() = ; diff --git a/ui-tests/type/generics/wrong_amount.stderr b/ui-tests/type/generics/wrong_amount.stderr index a668fbc..4728cd1 100644 --- a/ui-tests/type/generics/wrong_amount.stderr +++ b/ui-tests/type/generics/wrong_amount.stderr @@ -1,17 +1,24 @@ -/home/nils/projects/riverdelta/target/ast.js:110 - throw new Error(`substitution out of range, param index ${ty.idx} of param ${ty.name} out of range for length ${genericArgs.length}`); - ^ - -Error: substitution out of range, param index 0 of param T out of range for length 0 - at substituteTy (/home/nils/projects/riverdelta/target/ast.js:110:23) - at subst (/home/nils/projects/riverdelta/target/ast.js:106:27) - at Array.map () - at substituteTy (/home/nils/projects/riverdelta/target/ast.js:125:45) - at typeOfItem (/home/nils/projects/riverdelta/target/typeck/item.js:193:33) - at Object.itemInner (/home/nils/projects/riverdelta/target/typeck/index.js:63:54) - at Object.item (/home/nils/projects/riverdelta/target/ast.js:146:34) - at /home/nils/projects/riverdelta/target/ast.js:164:55 - at Array.map () - at foldAst (/home/nils/projects/riverdelta/target/ast.js:164:34) - -Node.js v20.10.0 +error: missing generics for type A, expected 1, but only 0 were passed + --> $DIR/wrong_amount.nil:9 +9 | a1: A, + ^ +error: missing generics for type A, expected 1, but only 0 were passed + --> $DIR/wrong_amount.nil:10 +10 | a2: A[], + ^ +error: missing generics for type B, expected 3, but only 0 were passed + --> $DIR/wrong_amount.nil:14 +14 | b1: B, + ^ +error: missing generics for type B, expected 3, but only 0 were passed + --> $DIR/wrong_amount.nil:15 +15 | b2: B[], + ^ +error: missing generics for type B, expected 3, but only 2 were passed + --> $DIR/wrong_amount.nil:16 +16 | b3: B[Int, Int], + ^ +error: type () does not take any generic arguments but 1 were passed + --> $DIR/wrong_amount.nil:22 +22 | c3: C[I32], + ^