mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-18 10:56:30 +00:00
karlkode generic type inferrence
This commit is contained in:
parent
5150090d2d
commit
a4c1b60a61
@ -1,5 +1,5 @@
|
|||||||
import { Pos } from "./token.ts";
|
import { Pos } from "./token.ts";
|
||||||
import { VType } from "./vtype.ts";
|
import { GenericArgsMap, VType } from "./vtype.ts";
|
||||||
|
|
||||||
export type Stmt = {
|
export type Stmt = {
|
||||||
kind: StmtKind;
|
kind: StmtKind;
|
||||||
@ -48,7 +48,12 @@ export type ExprKind =
|
|||||||
| { type: "group"; expr: Expr }
|
| { type: "group"; expr: Expr }
|
||||||
| { type: "field"; subject: Expr; ident: string }
|
| { type: "field"; subject: Expr; ident: string }
|
||||||
| { type: "index"; subject: Expr; value: Expr }
|
| { type: "index"; subject: Expr; value: Expr }
|
||||||
| { type: "call"; subject: Expr; args: Expr[] }
|
| {
|
||||||
|
type: "call";
|
||||||
|
subject: Expr;
|
||||||
|
args: Expr[];
|
||||||
|
genericArgs?: GenericArgsMap;
|
||||||
|
}
|
||||||
| { type: "path"; subject: Expr; ident: string }
|
| { type: "path"; subject: Expr; ident: string }
|
||||||
| { type: "etype_args"; subject: Expr; etypeArgs: EType[] }
|
| { type: "etype_args"; subject: Expr; etypeArgs: EType[] }
|
||||||
| { type: "unary"; unaryType: UnaryType; subject: Expr }
|
| { type: "unary"; unaryType: UnaryType; subject: Expr }
|
||||||
|
@ -414,23 +414,111 @@ export class Checker {
|
|||||||
const subject = this.checkExpr(expr.kind.subject);
|
const subject = this.checkExpr(expr.kind.subject);
|
||||||
if (subject.type === "error") return subject;
|
if (subject.type === "error") return subject;
|
||||||
if (subject.type === "fn") {
|
if (subject.type === "fn") {
|
||||||
if (subject.genericParams !== undefined) {
|
if (expr.kind.args.length !== subject.params.length) {
|
||||||
throw new Error("😭😭😭");
|
|
||||||
}
|
|
||||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
|
||||||
if (args.length !== subject.params.length) {
|
|
||||||
this.report(
|
this.report(
|
||||||
`incorrect number of arguments` +
|
`incorrect number of arguments` +
|
||||||
`, expected ${subject.params.length}`,
|
`, expected ${subject.params.length}`,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
||||||
|
if (subject.genericParams === undefined) {
|
||||||
|
return this.checkCallExprNoGenericsTail(
|
||||||
|
expr,
|
||||||
|
subject,
|
||||||
|
args,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.checkCallExprInferredGenericsTail(
|
||||||
|
expr,
|
||||||
|
subject,
|
||||||
|
args,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (subject.type === "generic_spec" && subject.subject.type === "fn") {
|
||||||
|
return this.checkCallExprExplicitGenericsTail(expr, subject);
|
||||||
|
}
|
||||||
|
this.report("cannot call non-fn", pos);
|
||||||
|
return { type: "error" };
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkCallExprNoGenericsTail(
|
||||||
|
expr: Expr,
|
||||||
|
subject: VType,
|
||||||
|
args: VType[],
|
||||||
|
pos: Pos,
|
||||||
|
): VType {
|
||||||
|
if (
|
||||||
|
expr.kind.type !== "call" || subject.type !== "fn"
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
for (let i = 0; i < args.length; ++i) {
|
||||||
|
if (this.vtypeContainsGeneric(args[i])) {
|
||||||
|
this.report(
|
||||||
|
`amfibious generic parameter for argument ${i}, please specify generic types explicitly`,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
return { type: "error" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < args.length; ++i) {
|
for (let i = 0; i < args.length; ++i) {
|
||||||
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
|
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
|
||||||
|
this.report(
|
||||||
|
`incorrect argument ${i} '${subject.params[i].ident}'` +
|
||||||
|
`, expected ${vtypeToString(subject.params[i].vtype)}` +
|
||||||
|
`, got ${vtypeToString(args[i])}`,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subject.returnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkCallExprInferredGenericsTail(
|
||||||
|
expr: Expr,
|
||||||
|
subject: VType,
|
||||||
|
args: VType[],
|
||||||
|
pos: Pos,
|
||||||
|
): VType {
|
||||||
|
if (
|
||||||
|
expr.kind.type !== "call" || subject.type !== "fn" ||
|
||||||
|
subject.genericParams === undefined
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const genericArgsRes = this.inferGenericArgs(
|
||||||
|
subject.genericParams,
|
||||||
|
subject.params,
|
||||||
|
args,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
if (!genericArgsRes.ok) {
|
||||||
|
return { type: "error" };
|
||||||
|
}
|
||||||
|
const genericArgs = genericArgsRes.value;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; ++i) {
|
||||||
|
const vtypeCompatible = vtypesEqual(
|
||||||
|
args[i],
|
||||||
|
subject.params[i].vtype,
|
||||||
|
genericArgs,
|
||||||
|
);
|
||||||
|
if (!vtypeCompatible) {
|
||||||
this.report(
|
this.report(
|
||||||
`incorrect argument ${i} '${subject.params[i].ident}'` +
|
`incorrect argument ${i} '${subject.params[i].ident}'` +
|
||||||
`, expected ${
|
`, expected ${
|
||||||
vtypeToString(subject.params[i].vtype)
|
vtypeToString(
|
||||||
|
extractGenericType(
|
||||||
|
subject.params[i].vtype,
|
||||||
|
genericArgs,
|
||||||
|
),
|
||||||
|
)
|
||||||
}` +
|
}` +
|
||||||
`, got ${vtypeToString(args[i])}`,
|
`, got ${vtypeToString(args[i])}`,
|
||||||
pos,
|
pos,
|
||||||
@ -438,9 +526,99 @@ export class Checker {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subject.returnType;
|
|
||||||
|
expr.kind.genericArgs = genericArgs;
|
||||||
|
return this.concretizeVType(subject.returnType, genericArgs);
|
||||||
}
|
}
|
||||||
if (subject.type === "generic_spec" && subject.subject.type === "fn") {
|
|
||||||
|
private inferGenericArgs(
|
||||||
|
genericParams: VTypeGenericParam[],
|
||||||
|
params: VTypeParam[],
|
||||||
|
args: VType[],
|
||||||
|
pos: Pos,
|
||||||
|
): { ok: true; value: GenericArgsMap } | { ok: false } {
|
||||||
|
const genericArgs: GenericArgsMap = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < params.length; ++i) {
|
||||||
|
if (!this.vtypeContainsGeneric(params[i].vtype)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
a: generic,
|
||||||
|
b: concrete,
|
||||||
|
} = this.reduceToSignificant(params[i].vtype, args[i]);
|
||||||
|
if (generic.type !== "generic") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const paramId = generic.param.id;
|
||||||
|
if (
|
||||||
|
paramId in genericArgs &&
|
||||||
|
!vtypesEqual(genericArgs[paramId], concrete)
|
||||||
|
) {
|
||||||
|
this.report(
|
||||||
|
`according to inferrence, argument ${i} has a conflicting type`,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
genericArgs[paramId] = concrete;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const param of genericParams) {
|
||||||
|
if (!(param.id in genericArgs)) {
|
||||||
|
this.report(`could not infer generic type ${param.ident}`, pos);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, value: genericArgs };
|
||||||
|
}
|
||||||
|
|
||||||
|
private reduceToSignificant(a: VType, b: VType): { a: VType; b: VType } {
|
||||||
|
if (a.type !== b.type) {
|
||||||
|
return { a, b };
|
||||||
|
}
|
||||||
|
if (a.type === "array" && b.type === "array") {
|
||||||
|
return this.reduceToSignificant(a.inner, b.inner);
|
||||||
|
}
|
||||||
|
throw new Error("idk what to do here");
|
||||||
|
}
|
||||||
|
|
||||||
|
private vtypeContainsGeneric(vtype: VType): boolean {
|
||||||
|
switch (vtype.type) {
|
||||||
|
case "error":
|
||||||
|
case "string":
|
||||||
|
case "unknown":
|
||||||
|
case "null":
|
||||||
|
case "int":
|
||||||
|
case "bool":
|
||||||
|
return false;
|
||||||
|
case "array":
|
||||||
|
return this.vtypeContainsGeneric(vtype.inner);
|
||||||
|
case "struct":
|
||||||
|
return vtype.fields.some((field) =>
|
||||||
|
this.vtypeContainsGeneric(field.vtype)
|
||||||
|
);
|
||||||
|
case "fn":
|
||||||
|
throw new Error("not implemented");
|
||||||
|
case "generic":
|
||||||
|
return true;
|
||||||
|
case "generic_spec":
|
||||||
|
throw new Error("listen kid, grrrrrrrr");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkCallExprExplicitGenericsTail(
|
||||||
|
expr: Expr,
|
||||||
|
subject: VType,
|
||||||
|
): VType {
|
||||||
|
if (
|
||||||
|
expr.kind.type !== "call" || subject.type !== "generic_spec" ||
|
||||||
|
subject.subject.type !== "fn"
|
||||||
|
) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const pos = expr.pos;
|
||||||
const inner = subject.subject;
|
const inner = subject.subject;
|
||||||
const params = inner.params;
|
const params = inner.params;
|
||||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
||||||
@ -474,13 +652,46 @@ export class Checker {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return extractGenericType(
|
|
||||||
|
expr.kind.genericArgs = subject.genericArgs;
|
||||||
|
return this.concretizeVType(
|
||||||
subject.subject.returnType,
|
subject.subject.returnType,
|
||||||
subject.genericArgs,
|
subject.genericArgs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.report("cannot call non-fn", pos);
|
|
||||||
return { type: "error" };
|
private concretizeVType(
|
||||||
|
vtype: VType,
|
||||||
|
generics: GenericArgsMap,
|
||||||
|
): VType {
|
||||||
|
switch (vtype.type) {
|
||||||
|
case "error":
|
||||||
|
case "unknown":
|
||||||
|
case "string":
|
||||||
|
case "null":
|
||||||
|
case "int":
|
||||||
|
case "bool":
|
||||||
|
return vtype;
|
||||||
|
case "array":
|
||||||
|
return {
|
||||||
|
type: "array",
|
||||||
|
inner: this.concretizeVType(vtype.inner, generics),
|
||||||
|
};
|
||||||
|
case "struct":
|
||||||
|
return {
|
||||||
|
type: "struct",
|
||||||
|
fields: vtype.fields.map((field) => ({
|
||||||
|
...field,
|
||||||
|
vtype: this.concretizeVType(field.vtype, generics),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
case "fn":
|
||||||
|
throw new Error("not implemented");
|
||||||
|
case "generic":
|
||||||
|
return generics[vtype.param.id];
|
||||||
|
case "generic_spec":
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkPathExpr(expr: Expr): VType {
|
public checkPathExpr(expr: Expr): VType {
|
||||||
|
@ -50,7 +50,7 @@ export class Compiler {
|
|||||||
|
|
||||||
const lowerer = new Lowerer(monoFns, callMap, lexer.currentPos());
|
const lowerer = new Lowerer(monoFns, callMap, lexer.currentPos());
|
||||||
const { program, fnNames } = lowerer.lower();
|
const { program, fnNames } = lowerer.lower();
|
||||||
//lowerer.printProgram();
|
lowerer.printProgram();
|
||||||
|
|
||||||
return { program, fnNames };
|
return { program, fnNames };
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { AstVisitor, visitExpr, VisitRes, visitStmts } from "./ast_visitor.ts";
|
|||||||
import { GenericArgsMap, VType } from "./vtype.ts";
|
import { GenericArgsMap, VType } from "./vtype.ts";
|
||||||
|
|
||||||
export class Monomorphizer {
|
export class Monomorphizer {
|
||||||
|
private fnIdCounter = 0;
|
||||||
private fns: MonoFnsMap = {};
|
private fns: MonoFnsMap = {};
|
||||||
private callMap: MonoCallNameGenMap = {};
|
private callMap: MonoCallNameGenMap = {};
|
||||||
private allFns: Map<number, Stmt>;
|
private allFns: Map<number, Stmt>;
|
||||||
@ -22,11 +23,13 @@ export class Monomorphizer {
|
|||||||
stmt: Stmt,
|
stmt: Stmt,
|
||||||
genericArgs?: GenericArgsMap,
|
genericArgs?: GenericArgsMap,
|
||||||
): MonoFn {
|
): MonoFn {
|
||||||
const nameGen = monoFnNameGen(stmt, genericArgs);
|
const id = this.fnIdCounter;
|
||||||
|
this.fnIdCounter += 1;
|
||||||
|
const nameGen = monoFnNameGen(id, stmt, genericArgs);
|
||||||
if (nameGen in this.fns) {
|
if (nameGen in this.fns) {
|
||||||
return this.fns[nameGen];
|
return this.fns[nameGen];
|
||||||
}
|
}
|
||||||
const monoFn = { nameGen, stmt, genericArgs };
|
const monoFn = { id, nameGen, stmt, genericArgs };
|
||||||
this.fns[nameGen] = monoFn;
|
this.fns[nameGen] = monoFn;
|
||||||
const calls = new CallCollector().collect(stmt);
|
const calls = new CallCollector().collect(stmt);
|
||||||
for (const call of calls) {
|
for (const call of calls) {
|
||||||
@ -34,7 +37,10 @@ export class Monomorphizer {
|
|||||||
if (call.kind.type !== "call") {
|
if (call.kind.type !== "call") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
if (call.kind.subject.vtype?.type === "fn") {
|
if (
|
||||||
|
call.kind.subject.vtype?.type === "fn" &&
|
||||||
|
call.kind.subject.vtype.genericParams === undefined
|
||||||
|
) {
|
||||||
const fn = this.allFns.get(call.kind.subject.vtype.stmtId);
|
const fn = this.allFns.get(call.kind.subject.vtype.stmtId);
|
||||||
if (fn === undefined) {
|
if (fn === undefined) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -43,6 +49,40 @@ export class Monomorphizer {
|
|||||||
this.callMap[call.id] = monoFn.nameGen;
|
this.callMap[call.id] = monoFn.nameGen;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
call.kind.subject.vtype?.type === "fn" &&
|
||||||
|
call.kind.subject.vtype.genericParams !== undefined
|
||||||
|
) {
|
||||||
|
if (call.kind.genericArgs === undefined) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const genericArgs = call.kind.genericArgs;
|
||||||
|
|
||||||
|
const monoArgs: GenericArgsMap = {};
|
||||||
|
for (const key in call.kind.genericArgs) {
|
||||||
|
const vtype = genericArgs[key];
|
||||||
|
if (vtype.type === "generic") {
|
||||||
|
if (genericArgs === undefined) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
monoArgs[key] = genericArgs[vtype.param.id];
|
||||||
|
} else {
|
||||||
|
monoArgs[key] = vtype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fnType = call.kind.subject.vtype!;
|
||||||
|
if (fnType.type !== "fn") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const fn = this.allFns.get(fnType.stmtId);
|
||||||
|
if (fn === undefined) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const monoFn = this.monomorphizeFn(fn, monoArgs);
|
||||||
|
this.callMap[call.id] = monoFn.nameGen;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (call.kind.subject.vtype?.type === "generic_spec") {
|
if (call.kind.subject.vtype?.type === "generic_spec") {
|
||||||
const genericSpecType = call.kind.subject.vtype!;
|
const genericSpecType = call.kind.subject.vtype!;
|
||||||
if (genericSpecType.subject.type !== "fn") {
|
if (genericSpecType.subject.type !== "fn") {
|
||||||
@ -85,6 +125,7 @@ export type MonoResult = {
|
|||||||
export type MonoFnsMap = { [nameGen: string]: MonoFn };
|
export type MonoFnsMap = { [nameGen: string]: MonoFn };
|
||||||
|
|
||||||
export type MonoFn = {
|
export type MonoFn = {
|
||||||
|
id: number;
|
||||||
nameGen: string;
|
nameGen: string;
|
||||||
stmt: Stmt;
|
stmt: Stmt;
|
||||||
genericArgs?: GenericArgsMap;
|
genericArgs?: GenericArgsMap;
|
||||||
@ -92,7 +133,11 @@ export type MonoFn = {
|
|||||||
|
|
||||||
export type MonoCallNameGenMap = { [exprId: number]: string };
|
export type MonoCallNameGenMap = { [exprId: number]: string };
|
||||||
|
|
||||||
function monoFnNameGen(stmt: Stmt, genericArgs?: GenericArgsMap): string {
|
function monoFnNameGen(
|
||||||
|
id: number,
|
||||||
|
stmt: Stmt,
|
||||||
|
genericArgs?: GenericArgsMap,
|
||||||
|
): string {
|
||||||
if (stmt.kind.type !== "fn") {
|
if (stmt.kind.type !== "fn") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
@ -100,12 +145,12 @@ function monoFnNameGen(stmt: Stmt, genericArgs?: GenericArgsMap): string {
|
|||||||
return "main";
|
return "main";
|
||||||
}
|
}
|
||||||
if (genericArgs === undefined) {
|
if (genericArgs === undefined) {
|
||||||
return `${stmt.kind.ident}_${stmt.id}`;
|
return `${stmt.kind.ident}_${id}`;
|
||||||
}
|
}
|
||||||
const args = Object.values(genericArgs)
|
const args = Object.values(genericArgs)
|
||||||
.map((arg) => vtypeNameGenPart(arg))
|
.map((arg) => vtypeNameGenPart(arg))
|
||||||
.join("_");
|
.join("_");
|
||||||
return `${stmt.kind.ident}_${stmt.id}_${args}`;
|
return `${stmt.kind.ident}_${id}_${args}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function vtypeNameGenPart(vtype: VType): string {
|
function vtypeNameGenPart(vtype: VType): string {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
//
|
||||||
|
|
||||||
fn array_new<T>() -> [T] #[builtin(ArrayNew)] {}
|
fn array_new<T>() -> [T] #[builtin(ArrayNew)] {}
|
||||||
fn array_push<T>(array: [T], value: T) #[builtin(ArrayPush)] {}
|
fn array_push<T>(array: [T], value: T) #[builtin(ArrayPush)] {}
|
||||||
@ -8,12 +8,12 @@ fn array_at<T>(array: [T], index: int) -> string #[builtin(ArrayAt)] {}
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let strings = array_new::<string>();
|
let strings = array_new::<string>();
|
||||||
array_push::<string>(strings, "hello");
|
array_push(strings, "hello");
|
||||||
array_push::<string>(strings, "world");
|
array_push(strings, "world");
|
||||||
|
|
||||||
let ints = array_new::<int>();
|
let ints = array_new::<int>();
|
||||||
array_push::<int>(ints, 1);
|
array_push(ints, 1);
|
||||||
array_push::<int>(ints, 2);
|
array_push(ints, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ fi
|
|||||||
|
|
||||||
echo Compiling $1...
|
echo Compiling $1...
|
||||||
|
|
||||||
deno run --allow-read --allow-write compiler/main.ts $1
|
deno run --allow-read --allow-write --check compiler/main.ts $1
|
||||||
|
|
||||||
echo Running out.slgbc...
|
echo Running out.slgbc...
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user