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

@ -10,7 +10,6 @@ import {
Folder,
LOGICAL_KINDS,
LoopId,
Resolution,
Resolved,
StructLiteralField,
Type,
@ -91,7 +90,6 @@ export function checkBody(
fnTy: TyFn,
): Expr<Typecked> {
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(

View file

@ -31,7 +31,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
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<Resolved>): Pkg<Typecked> {
};
}
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<Resolved>): Pkg<Typecked> {
};
}
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<Resolved>): Pkg<Typecked> {
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<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 { 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<Resolved>): 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<Resolved>(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<Resolved>): 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<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) {
// 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;
}