Start typechecking generics

This commit is contained in:
nora 2023-11-06 20:41:49 +01:00
parent bf73203182
commit 01d4238269
15 changed files with 248 additions and 19 deletions

View file

@ -1 +0,0 @@
mod b;

View file

@ -139,8 +139,9 @@ export type FunctionArg<P extends Phase> = {
export type ItemKindType<P extends Phase> = {
kind: "type";
generics: Ident[];
type: TypeDefKind<P>;
ty?: TyStruct;
ty?: Ty;
};
export type TypeDefKind<P extends Phase> =
@ -407,6 +408,7 @@ export const UNARY_KINDS: UnaryKind[] = ["!", "-"];
export type TypeKind<P extends Phase> =
| {
kind: "ident";
generics: Type<P>[];
value: IdentWithRes<P>;
}
| {
@ -453,6 +455,19 @@ export type Resolution =
kind: "builtin";
name: BuiltinName;
}
| {
kind: "tyParam";
/**
* The index of the type parameter, from first to last.
* ```
* type A[T, U] = (T, U);
* ^ ^
* 0 1
* ```
*/
index: number;
name: string;
}
| { kind: "error"; err: ErrorEmitted };
export const BUILTINS = [
@ -531,6 +546,8 @@ export type TyVar = {
export type TyStruct = {
kind: "struct";
itemId: ItemId;
params: string[];
args: Ty[];
_name: string;
fields: [string, Ty][];
};
@ -544,6 +561,17 @@ export type TyNever = {
kind: "never";
};
export type TyParam = {
kind: "param";
/**
* The index of the type parameter of the parent.
* If the parent is `type A[T, U] = U;`
* then `U` will have index 1.
*/
idx: number;
name: string;
};
export type TyError = {
kind: "error";
err: ErrorEmitted;
@ -561,6 +589,7 @@ export type Ty =
| TyStruct
| TyRawPtr
| TyNever
| TyParam
| TyError;
export function tyIsUnit(ty: Ty): ty is TyUnit {
@ -860,6 +889,7 @@ export function superFoldType<From extends Phase, To extends Phase>(
case "ident": {
return {
kind: "ident",
generics: type.generics.map((type) => folder.type(type)),
value: folder.ident(type.value),
span,
};
@ -895,3 +925,7 @@ export function superFoldType<From extends Phase, To extends Phase>(
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");
}

View file

@ -5,6 +5,8 @@ it("should compute struct layout correctly", () => {
const ty: TyStruct = {
kind: "struct",
itemId: ItemId.dummy(),
args: [],
params: [],
_name: "",
fields: [
["uwu", TY_I32],
@ -50,6 +52,8 @@ it("should compute single field struct layout correctly", () => {
const ty: TyStruct = {
kind: "struct",
itemId: ItemId.dummy(),
args: [],
params: [],
_name: "",
fields: [["owo", TY_INT]],
};

View file

@ -20,6 +20,7 @@ import {
superFoldItem,
varUnreachable,
TyRawPtr,
paramUnreachable,
} from "./ast";
import { GlobalContext } from "./context";
import { unreachable } from "./error";
@ -1257,6 +1258,8 @@ function argRetAbi(param: Ty): ArgRetAbi {
return [];
case "var":
varUnreachable();
case "param":
paramUnreachable();
case "error":
unreachable("codegen should not see errors");
}
@ -1314,6 +1317,8 @@ function wasmTypeForBody(ty: Ty): wasm.ValType[] {
return [];
case "var":
varUnreachable();
case "param":
paramUnreachable();
case "error":
unreachable("codegen should not see errors");
}

View file

@ -126,6 +126,10 @@ function parseItem(t: State): [State, Item<Parsed>] {
} else if (tok.kind === "type") {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
let generics;
[t, generics] = parseGenericsDef(t);
[t] = expectNext(t, "=");
let type: TypeDefKind<Parsed>;
@ -169,6 +173,7 @@ function parseItem(t: State): [State, Item<Parsed>] {
{
kind: "type",
name: name.ident,
generics,
type,
span: name.span,
id: ItemId.dummy(),
@ -308,7 +313,7 @@ function parseFunctionSig(t: State): [State, FunctionSig] {
let params: FunctionArg<Parsed>[];
[t, params] = parseCommaSeparatedList(t, ")", (t) => {
let name;
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
[t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, ":");
let type;
[t, type] = parseType(t);
@ -326,6 +331,33 @@ function parseFunctionSig(t: State): [State, FunctionSig] {
return [t, { name: name.ident, params, returnType }];
}
function parseGenericsDef(t: State): [State, Ident[]] {
let openBracket;
[t, openBracket] = eat(t, "[");
if (openBracket) {
let elems;
[t, elems] = parseCommaSeparatedList<Ident>(t, "]", (t) => {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
return [t, { name: name.ident, span: name.span }];
});
return [t, elems];
} else {
return [t, []];
}
}
function parseGenericsArgs(t: State): [State, Type<Parsed>[]] {
let openBracket;
[t, openBracket] = eat(t, "[");
if (openBracket) {
return parseCommaSeparatedList(t, "]", parseType);
} else {
return [t, []];
}
}
function parseExpr(t: State): [State, Expr<Parsed>] {
/*
EXPR = ASSIGNMENT
@ -656,10 +688,13 @@ function parseType(t: State): [State, Type<Parsed>] {
return [t, { kind: "never", span }];
}
case "identifier": {
let generics;
[t, generics] = parseGenericsArgs(t);
return [
t,
{
kind: "ident",
generics,
value: { name: tok.ident, span },
span,
},

View file

@ -65,6 +65,11 @@ function printFunction(func: ItemFunction<AnyPhase>): string {
}
function printTypeDef(type: ItemType<AnyPhase>): string {
const head = `type ${type.name}${
type.generics.length === 0
? ""
: `[${type.generics.map((ident) => ident.name).join(", ")}]`
} = `;
switch (type.type.kind) {
case "struct": {
const { fields } = type.type;
@ -75,10 +80,10 @@ function printTypeDef(type: ItemType<AnyPhase>): string {
const fieldPart =
fields.length === 0 ? "{}" : `{\n${fieldStr.join("\n")}\n}`;
return `type ${type.name} = ${fieldPart};`;
return head + `${fieldPart};`;
}
case "alias": {
return `type ${type.name} = ${printType(type.type.type)}`;
return head + `${printType(type.type.type)}`;
}
}
}
@ -236,6 +241,9 @@ function printRes(res: Resolution): string {
case "builtin": {
return `#B`;
}
case "tyParam": {
return `#P${res.index}`;
}
case "error": {
return "#E";
}
@ -283,6 +291,9 @@ export function printTy(ty: Ty): string {
case "never": {
return "!";
}
case "param": {
return ty.name;
}
case "error":
return "<ERROR>";
}

View file

@ -107,6 +107,7 @@ function resolveModule(
});
const scopes: string[] = [];
let tyParamScopes: string[] = [];
const popScope = (expected: string) => {
const popped = scopes.pop();
@ -130,6 +131,18 @@ function resolveModule(
}
}
for (let i = tyParamScopes.length - 1; i >= 0; i--) {
const candidate = tyParamScopes[i];
if (candidate === ident.name) {
return {
kind: "tyParam",
index: i,
name: ident.name,
};
}
}
const item = items.get(ident.name);
if (item !== undefined) {
return {
@ -214,6 +227,13 @@ function resolveModule(
defPath,
};
}
case "type": {
tyParamScopes = item.generics.map(({name}) => name);
const type = { ...superFoldItem(item, this) };
return type;
}
}
return { ...superFoldItem(item, this), defPath };

View file

@ -25,7 +25,6 @@ import {
tyIsUnit,
Type,
Typecked,
TyStruct,
Item,
StructLiteralField,
superFoldExpr,
@ -133,20 +132,56 @@ function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty {
case "ident": {
const ident = type.value;
const res = ident.res;
const generics = type.generics.map((type) => lowerAstTy(cx, type));
let ty: Ty;
switch (res.kind) {
case "local": {
throw new Error("Item type cannot refer to local variable");
}
case "item": {
return typeOfItem(cx, res.id, type.span);
ty = typeOfItem(cx, res.id, type.span);
break;
}
case "builtin": {
return builtinAsTy(cx, res.name, ident.span);
ty = builtinAsTy(cx, res.name, ident.span);
break;
}
case "tyParam": {
ty = { kind: "param", idx: res.index, name: res.name };
break;
}
case "error": {
return tyErrorFrom(res);
ty = tyErrorFrom(res);
break;
}
}
if (ty.kind === "struct") {
if (generics.length === ty.params.length) {
return { ...ty, args: 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,
),
);
}
}
return ty;
}
case "list": {
return {
@ -247,6 +282,8 @@ function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty {
case "struct": {
ty = {
kind: "struct",
args: [],
params: item.generics.map((ident) => ident.name),
itemId: item.id,
_name: item.name,
fields: [
@ -264,6 +301,7 @@ function typeOfItem(cx: TypeckCtx, itemId: ItemId, cause: Span): Ty {
break;
}
case "alias": {
// TODO: subst
ty = lowerAstTy(cx, item.type.type);
break;
}
@ -372,7 +410,7 @@ export function typeck(
};
}
case "type": {
const ty = typeOfItem(cx, item.id, item.span) as TyStruct;
const ty = typeOfItem(cx, item.id, item.span);
switch (item.type.kind) {
case "struct": {
@ -699,6 +737,11 @@ function typeOfValue(fcx: FuncCtx, res: Resolution, span: Span): Ty {
}
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);
}

View file

@ -1,11 +1,11 @@
type A = struct { a: Int };
//@check-pass
type A[T] = struct { a: T };
type B[T, U, V] = struct {
b: T,
d: V,
};
type C = ();
function main() = (
let a: Int = "";
let b: Int = "";
c;
);
function main() = ;
function rawr(a: *A) = (
a.a = 1;
);
function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;

View file

@ -0,0 +1,3 @@
function main() = (
let a: I32[I32] = 0;
);

View file

@ -0,0 +1,4 @@
error: type I32 does not take generic arguments
--> $DIR/generics_on_primitive.nil:2
2 | let a: I32[I32] = 0;
^^^

View file

@ -0,0 +1,8 @@
type A[T] = struct { a: T };
type B[T, U, V] = struct {
b: T,
d: V,
};
type C = ();
function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;

View file

@ -0,0 +1,4 @@
error: `main` function not found
--> $DIR/structs.nil:1
1 | type A[T] = struct { a: T };
^

View file

@ -0,0 +1,23 @@
type A[T] = struct { a: T };
type B[T, U, V] = struct {
b: T,
d: V,
};
type C = ();
function test(
a1: A,
a2: A[],
a3: A[I32],
a4: A[I32, I32],
b1: B,
b2: B[],
b3: B[Int, Int],
b4: B[Int, I32, Int],
b5: B[Int, Int, Int, Int],
c1: C,
c2: C[],
c3: C[I32],
) = ;

View file

@ -0,0 +1,36 @@
error: expected 1 generic arguments, found 0
--> $DIR/wrong_amount.nil:9
9 | a1: A,
^
error: expected 1 generic arguments, found 0
--> $DIR/wrong_amount.nil:10
10 | a2: A[],
^
error: expected 1 generic arguments, found 2
--> $DIR/wrong_amount.nil:12
12 | a4: A[I32, I32],
^
error: expected 3 generic arguments, found 0
--> $DIR/wrong_amount.nil:14
14 | b1: B,
^
error: expected 3 generic arguments, found 0
--> $DIR/wrong_amount.nil:15
15 | b2: B[],
^
error: expected 3 generic arguments, found 2
--> $DIR/wrong_amount.nil:16
16 | b3: B[Int, Int],
^
error: expected 3 generic arguments, found 4
--> $DIR/wrong_amount.nil:18
18 | b5: B[Int, Int, Int, Int],
^
error: type () does not take generic arguments
--> $DIR/wrong_amount.nil:22
22 | c3: C[I32],
^
error: `main` function not found
--> $DIR/wrong_amount.nil:1
1 | type A[T] = struct { a: T };
^