mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 08:25:02 +01:00
Start typechecking generics
This commit is contained in:
parent
bf73203182
commit
01d4238269
15 changed files with 248 additions and 19 deletions
|
|
@ -1 +0,0 @@
|
|||
mod b;
|
||||
36
src/ast.ts
36
src/ast.ts
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
18
test.nil
18
test.nil
|
|
@ -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) = ;
|
||||
|
|
|
|||
3
ui-tests/type/generics/generics_on_primitive.nil
Normal file
3
ui-tests/type/generics/generics_on_primitive.nil
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
function main() = (
|
||||
let a: I32[I32] = 0;
|
||||
);
|
||||
4
ui-tests/type/generics/generics_on_primitive.stderr
Normal file
4
ui-tests/type/generics/generics_on_primitive.stderr
Normal 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;
|
||||
^^^
|
||||
8
ui-tests/type/generics/structs.nil
Normal file
8
ui-tests/type/generics/structs.nil
Normal 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) = ;
|
||||
4
ui-tests/type/generics/structs.stderr
Normal file
4
ui-tests/type/generics/structs.stderr
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
error: `main` function not found
|
||||
--> $DIR/structs.nil:1
|
||||
1 | type A[T] = struct { a: T };
|
||||
^
|
||||
23
ui-tests/type/generics/wrong_amount.nil
Normal file
23
ui-tests/type/generics/wrong_amount.nil
Normal 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],
|
||||
) = ;
|
||||
36
ui-tests/type/generics/wrong_amount.stderr
Normal file
36
ui-tests/type/generics/wrong_amount.stderr
Normal 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 };
|
||||
^
|
||||
Loading…
Add table
Add a link
Reference in a new issue