fix generics

This commit is contained in:
nora 2023-12-16 13:45:09 +01:00
parent dbd49d852f
commit 66d95dfeeb
18 changed files with 154 additions and 158 deletions

View file

@ -141,7 +141,7 @@ export type FunctionArg<P extends Phase> = {
export type ItemKindType<P extends Phase> = { export type ItemKindType<P extends Phase> = {
kind: "type"; kind: "type";
generics: Ident[]; genericParams: Ident[];
type: TypeDefKind<P>; type: TypeDefKind<P>;
ty?: Ty; ty?: Ty;
}; };
@ -417,7 +417,7 @@ export const UNARY_KINDS: UnaryKind[] = ["!", "-"];
export type TypeKind<P extends Phase> = export type TypeKind<P extends Phase> =
| { | {
kind: "ident"; kind: "ident";
generics: Type<P>[]; genericArgs: Type<P>[];
value: IdentWithRes<P>; value: IdentWithRes<P>;
} }
| { | {
@ -793,7 +793,7 @@ export function superFoldType<From extends Phase, To extends Phase>(
case "ident": { case "ident": {
return { return {
kind: "ident", kind: "ident",
generics: type.generics.map((type) => folder.type(type)), genericArgs: type.genericArgs.map((type) => folder.type(type)),
value: folder.ident(type.value), value: folder.ident(type.value),
span, span,
}; };

View file

@ -7,7 +7,6 @@ it("should compute struct layout correctly", () => {
kind: "struct", kind: "struct",
itemId: ItemId.dummy(), itemId: ItemId.dummy(),
genericArgs: [], genericArgs: [],
params: [],
_name: "", _name: "",
fields_no_subst: [ fields_no_subst: [
["uwu", TYS.I32], ["uwu", TYS.I32],
@ -54,7 +53,6 @@ it("should compute single field struct layout correctly", () => {
kind: "struct", kind: "struct",
itemId: ItemId.dummy(), itemId: ItemId.dummy(),
genericArgs: [], genericArgs: [],
params: [],
_name: "", _name: "",
fields_no_subst: [["owo", TYS.INT]], fields_no_subst: [["owo", TYS.INT]],
}; };

View file

@ -384,7 +384,6 @@ function lowerFunc(cx: Context, func: ItemFunction<Typecked>) {
fcx.wasm.body = body.instructions; fcx.wasm.body = body.instructions;
} else { } else {
lowerExpr(fcx, wasmFunc.body, body); lowerExpr(fcx, wasmFunc.body, body);
paramLocations.forEach((local) => { paramLocations.forEach((local) => {
const refcount = needsRefcount(local.ty); const refcount = needsRefcount(local.ty);
if (refcount !== undefined) { if (refcount !== undefined) {
@ -1264,7 +1263,6 @@ function argRetAbi(param: Ty): ArgRetAbi {
return []; return [];
case "var": case "var":
case "param": case "param":
case "alias":
case "error": case "error":
codegenUnreachableTy(param); codegenUnreachableTy(param);
} }
@ -1320,7 +1318,6 @@ function wasmTypeForBody(ty: Ty): wasm.ValType[] {
return []; return [];
case "var": case "var":
case "param": case "param":
case "alias":
case "error": case "error":
codegenUnreachableTy(ty); codegenUnreachableTy(ty);
} }

View file

@ -173,7 +173,7 @@ function parseItem(t: State): [State, Item<Parsed>] {
{ {
kind: "type", kind: "type",
name: name.ident, name: name.ident,
generics, genericParams: generics,
type, type,
span: name.span, span: name.span,
id: ItemId.dummy(), id: ItemId.dummy(),
@ -694,7 +694,7 @@ function parseType(t: State): [State, Type<Parsed>] {
t, t,
{ {
kind: "ident", kind: "ident",
generics, genericArgs: generics,
value: { name: tok.ident, span }, value: { name: tok.ident, span },
span, span,
}, },

View file

@ -65,9 +65,9 @@ function printFunction(func: ItemFunction<AnyPhase>): string {
function printTypeDef(type: ItemType<AnyPhase>): string { function printTypeDef(type: ItemType<AnyPhase>): string {
const head = `type ${type.name}${ 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) { switch (type.type.kind) {
case "struct": { case "struct": {
@ -284,8 +284,6 @@ export function printTy(ty: Ty): string {
return "!"; return "!";
case "param": case "param":
return ty.name; return ty.name;
case "alias":
return printTy(substituteTy(ty.genericArgs, ty.actual));
case "error": case "error":
return "<ERROR>"; return "<ERROR>";
} }

View file

@ -225,7 +225,7 @@ function resolveModule(
}; };
} }
case "type": { case "type": {
tyParamScopes = item.generics.map(({ name }) => name); tyParamScopes = item.genericParams.map(({ name }) => name);
const type = { ...superFoldItem(item, this) }; const type = { ...superFoldItem(item, this) };

View file

@ -10,7 +10,6 @@ import {
Folder, Folder,
LOGICAL_KINDS, LOGICAL_KINDS,
LoopId, LoopId,
Resolution,
Resolved, Resolved,
StructLiteralField, StructLiteralField,
Type, Type,
@ -91,7 +90,6 @@ export function checkBody(
fnTy: TyFn, fnTy: TyFn,
): Expr<Typecked> { ): Expr<Typecked> {
const infcx = new InferContext(cx.gcx.error); const infcx = new InferContext(cx.gcx.error);
const fcx: FuncCtx = { const fcx: FuncCtx = {
cx, cx,
infcx, infcx,
@ -246,7 +244,8 @@ export function checkBody(
break; break;
} }
case "item": { 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; break;
} }
case "builtin": case "builtin":
@ -592,7 +591,7 @@ function checkStructLiteral(
} }
// TODO: Handle generic arugments // 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") { if (structTy.kind !== "struct") {
const err: ErrorEmitted = emitError( const err: ErrorEmitted = emitError(

View file

@ -31,7 +31,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
switch (item.kind) { switch (item.kind) {
case "function": { case "function": {
// Functions do not have generic arguments right now. // 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); const body = checkBody(cx, ast, item.body, fnTy);
return { return {
@ -43,7 +43,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
}; };
} }
case "import": { 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) => { fnTy.params.forEach((param, i) => {
switch (param.kind) { switch (param.kind) {
@ -87,7 +87,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
}; };
} }
case "type": { case "type": {
const ty = typeOfItem(cx, item.id, [], item.span); const ty = typeOfItem(cx, item.id, item.span);
switch (item.type.kind) { switch (item.type.kind) {
case "struct": { case "struct": {
@ -135,7 +135,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
return item; return item;
} }
case "global": { case "global": {
const ty = typeOfItem(cx, item.id, [], item.span); const ty = typeOfItem(cx, item.id, item.span);
const { init } = item; const { init } = item;
let initChecked: Expr<Typecked>; let initChecked: Expr<Typecked>;

View file

@ -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 { CompilerError, Span } from "../error";
import { printTy } from "../printer"; import { printTy } from "../printer";
import { TYS, Ty, substituteTy } from "../types"; import { TYS, Ty, createIdentityGenericArgs, substituteTy } from "../types";
import { TypeckCtx, tyError, tyErrorFrom } from "./base"; import { TypeckCtx, tyError, tyErrorFrom } from "./base";
function builtinAsTy(cx: TypeckCtx, name: string, span: Span): Ty { function builtinAsTy(cx: TypeckCtx, name: string, span: Span): Ty {
@ -36,59 +36,78 @@ export function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty {
const ident = type.value; const ident = type.value;
const res = ident.res; 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 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) { switch (res.kind) {
case "local": { case "local": {
throw new Error("Item type cannot refer to local variable"); throw new Error("Item type cannot refer to local variable");
} }
case "item": { case "item": {
ty = typeOfItem(cx, res.id, generics, type.span); ty = typeOfItem(cx, res.id, type.span);
const item = cx.gcx.findItem<Resolved>(res.id, cx.ast);
if (item.kind === "type" && item.type.kind === "alias") {
isAlias = true;
}
generics = itemGenerics(item);
break; break;
} }
case "builtin": { case "builtin": {
ty = builtinAsTy(cx, res.name, ident.span); ty = builtinAsTy(cx, res.name, ident.span);
generics = { kind: "none" };
break; break;
} }
case "tyParam": { case "tyParam": {
ty = { kind: "param", idx: res.index, name: res.name }; ty = { kind: "param", idx: res.index, name: res.name };
generics = { kind: "none" };
break; break;
} }
case "error": { case "error": {
ty = tyErrorFrom(res); // Skip generics validation, it's fine!
break; return tyErrorFrom(res);
} }
} }
if (ty.kind === "struct" || ty.kind === "alias") { if (
if (generics.length === ty.params.length) { (generics.kind === "none" || generics.params.length === 0) &&
if (ty.kind === "alias") { genericArgs.length > 0
return substituteTy(ty.genericArgs, ty.actual); ) {
return tyError(
cx,
new CompilerError(
`type ${printTy(ty)} does not take any generic arguments but ${
genericArgs.length
} were passed`,
type.span,
),
);
} }
return { ...ty, genericArgs: generics }; 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 { } else {
return tyError( if (ty.kind === "struct") {
cx, return { ...ty, genericArgs };
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,
),
);
}
}
return ty; return ty;
} }
}
case "tuple": { case "tuple": {
return { return {
kind: "tuple", kind: "tuple",
@ -115,12 +134,31 @@ export function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty {
} }
} }
export function typeOfItem( type Generics =
cx: TypeckCtx, | {
itemId: ItemId, kind: "none";
genericArgs: Ty[], }
cause: Span, | {
): Ty { kind: "some";
params: Ident[];
};
function itemGenerics(item: Item<Typecked> | Item<Resolved>): 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) { if (itemId.pkgId !== cx.ast.id) {
// Look up foreign items in the foreign pkgs, we don't need to lower those // Look up foreign items in the foreign pkgs, we don't need to lower those
// ourselves. // ourselves.
@ -131,7 +169,7 @@ export function typeOfItem(
case "import": case "import":
case "type": case "type":
case "global": case "global":
return substituteTy(genericArgs, item.ty!); return item.ty!;
case "mod": { case "mod": {
return tyError( return tyError(
cx, cx,
@ -187,14 +225,7 @@ export function typeOfItem(
case "struct": { case "struct": {
ty = { ty = {
kind: "struct", kind: "struct",
genericArgs: item.generics.map( genericArgs: createIdentityGenericArgs(item.genericParams),
({ name }, idx): Ty => ({
kind: "param",
name,
idx,
}),
),
params: item.generics.map((ident) => ident.name),
itemId: item.id, itemId: item.id,
_name: item.name, _name: item.name,
fields_no_subst: [ fields_no_subst: [
@ -214,52 +245,41 @@ export function typeOfItem(
case "alias": { case "alias": {
const actual = lowerAstTy(cx, item.type.type); const actual = lowerAstTy(cx, item.type.type);
ty = { ty = actual;
kind: "alias",
actual,
genericArgs: item.generics.map(
({ name }, idx): Ty => ({
kind: "param",
name,
idx,
}),
),
params: item.generics.map((ident) => ident.name),
};
break; break;
} }
} }
break; break;
} }
case "mod": { case "mod": {
return tyError( ty = tyError(
cx, cx,
new CompilerError( new CompilerError(
`module ${item.name} cannot be used as a type or value`, `module ${item.name} cannot be used as a type or value`,
cause, cause,
), ),
); );
break;
} }
case "extern": { case "extern": {
return tyError( ty = tyError(
cx, cx,
new CompilerError( new CompilerError(
`extern declaration ${item.name} cannot be used as a type or value`, `extern declaration ${item.name} cannot be used as a type or value`,
cause, cause,
), ),
); );
break;
} }
case "global": { case "global": {
ty = lowerAstTy(cx, item.type); ty = lowerAstTy(cx, item.type);
break; break;
} }
case "error": { case "error": {
return tyErrorFrom(item); ty = tyErrorFrom(item);
} }
} }
ty = substituteTy(genericArgs, ty);
cx.itemTys.set(item.id, ty); cx.itemTys.set(item.id, ty);
return ty; return ty;
} }

View file

@ -1,4 +1,4 @@
import { ItemId, Resolution } from "./ast"; import { Ident, ItemId, Resolution } from "./ast";
import { ErrorEmitted } from "./error"; import { ErrorEmitted } from "./error";
export type TyString = { export type TyString = {
@ -41,7 +41,6 @@ export type TyVar = {
export type TyStruct = { export type TyStruct = {
kind: "struct"; kind: "struct";
itemId: ItemId; itemId: ItemId;
params: string[];
genericArgs: Ty[]; genericArgs: Ty[];
_name: string; _name: string;
fields_no_subst: [string, Ty][]; fields_no_subst: [string, Ty][];
@ -67,13 +66,6 @@ export type TyParam = {
name: string; name: string;
}; };
export type TyAlias = {
kind: "alias";
actual: Ty;
genericArgs: Ty[];
params: string[];
};
export type TyError = { export type TyError = {
kind: "error"; kind: "error";
err: ErrorEmitted; err: ErrorEmitted;
@ -91,7 +83,6 @@ export type Ty =
| TyRawPtr | TyRawPtr
| TyNever | TyNever
| TyParam | TyParam
| TyAlias
| TyError; | TyError;
export function tyIsUnit(ty: Ty): ty is TyUnit { 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), params: ty.params.map(subst),
}; };
case "struct": case "struct":
case "alias":
return { return {
...ty, ...ty,
genericArgs: ty.genericArgs.map(subst), genericArgs: ty.genericArgs.map(subst),
@ -158,3 +148,13 @@ export function substituteTy(genericArgs: Ty[], ty: Ty): Ty {
return ty; return ty;
} }
} }
export function createIdentityGenericArgs(params: Ident[]): Ty[] {
return params.map(
(name, idx): Ty => ({
kind: "param",
name: name.name,
idx,
}),
);
}

View file

@ -1,8 +1,8 @@
type A[T] = struct { a: T };
type B[T] = struct {
b: T,
};
function main() = ; function main() = ;
type A[T] = T; function test(b: B[I32]) = ;
type B[T] = struct { a: T };
//function ohno(x: A[A[A[I32]]]): I32 = x;
function generic(a: B[I32]): B[I32] = a;

View file

@ -0,0 +1,6 @@
//@check-pass
type A[T] = T;
function ohno(x: A[A[A[I32]]]): I32 = x;
function main() = ;

View file

@ -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 --> $DIR/generics_on_primitive.nil:2
2 | let a: I32[I32] = 0; 2 | let a: I32[I32] = 0;
^^^ ^^^

View file

@ -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 (<anonymous>)
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 (<anonymous>)
at foldAst (/home/nils/projects/riverdelta/target/ast.js:164:34)
Node.js v20.10.0

View file

@ -1,3 +1,4 @@
//@check-pass
type A[T] = struct { a: T }; type A[T] = struct { a: T };
type B[T, U, V] = struct { type B[T, U, V] = struct {
b: T, b: T,
@ -6,3 +7,5 @@ type B[T, U, V] = struct {
type C = (); type C = ();
function test(a: A[I32], b: B[I32, Int, I32], c: C) = ; function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;
function main() = ;

View file

@ -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 (<anonymous>)
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 (<anonymous>)
at foldAst (/home/nils/projects/riverdelta/target/ast.js:164:34)
Node.js v20.10.0

View file

@ -21,3 +21,5 @@ function test(
c2: C[], c2: C[],
c3: C[I32], c3: C[I32],
) = ; ) = ;
function main() = ;

View file

@ -1,17 +1,24 @@
/home/nils/projects/riverdelta/target/ast.js:110 error: missing generics for type A, expected 1, but only 0 were passed
throw new Error(`substitution out of range, param index ${ty.idx} of param ${ty.name} out of range for length ${genericArgs.length}`); --> $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],
^ ^
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 (<anonymous>)
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 (<anonymous>)
at foldAst (/home/nils/projects/riverdelta/target/ast.js:164:34)
Node.js v20.10.0