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],
+ ^