Fixing some unchecked errors

This commit is contained in:
Dustin Swan 2026-03-26 18:55:20 -06:00
parent f272ffaca2
commit b97eb52c21
No known key found for this signature in database
GPG key ID: 30D46587E2100467
2 changed files with 50 additions and 21 deletions

View file

@ -88,6 +88,15 @@ function infer(expr: AST, env: TypeEnv, subst: Subst): TypeAST | null {
return t ? applySubst(t, subst) : null;
}
case 'list': {
if (expr.elements.length === 0) {
return { kind: 'type-apply', constructor: { kind: 'type-name', name: 'List' }, args: [{ kind: 'type-var', name: '_empty' }] };
}
const first = infer(expr.elements[0], env, subst);
if (!first) return null;
return { kind: 'type-apply', constructor: { kind: 'type-name', name: 'List' }, args: [first] };
}
case 'record': {
const fields: { name: string, type: TypeAST }[] = [];
for (const entry of expr.entries) {
@ -173,7 +182,10 @@ function check(expr: AST, expected: TypeAST, env: TypeEnv, subst: Subst): string
const scrutType = infer(expr.expr, env, subst);
for (const c of expr.cases) {
const caseEnv = new Map(env);
if (scrutType) bindPattern(c.pattern, scrutType, caseEnv, subst);
if (scrutType) {
const patErr = bindPattern(c.pattern, scrutType, caseEnv, subst);
if (patErr) warn(patErr, c.result);
}
const err = check(c.result, expected, caseEnv, subst);
if (err) warn(err, c.result);
}
@ -188,6 +200,17 @@ function check(expr: AST, expected: TypeAST, env: TypeEnv, subst: Subst): string
return check(expr.body, expected, newEnv, subst);
}
// List literal against List type
if (expr.kind === 'list' && exp.kind === 'type-apply' && exp.constructor.kind === 'type-name' && exp.constructor.name === 'List') {
const elemType = exp.args[0];
for (const elem of expr.elements) {
if (elem.kind === 'list-spread') continue;
const err = check(elem, elemType, env, subst);
if (err) return err;
}
return null;
}
// Fallback: infer and unify
const inferred = infer(expr, env, subst);
if (!inferred) return null; // Can't infer, skip silently
@ -199,19 +222,37 @@ function warn(msg: string, expr: AST) {
console.warn(`TypeError${loc}: ${msg}`);
}
function bindPattern(pattern: Pattern, type: TypeAST, env: TypeEnv, subst: Subst): void {
function bindPattern(pattern: Pattern, type: TypeAST, env: TypeEnv, subst: Subst): string | null {
const t = applySubst(type, subst);
switch (pattern.kind) {
case 'var':
env.set(pattern.name, t);
break;
return null;
case 'wildcard':
case 'literal':
return null;
case 'constructor':
// TODO: look up ctor arg types
break;
case 'list':
case 'list-spread':
// TODO: bind element types
break;
if (t.kind === 'type-apply' && t.constructor.kind === 'type-name' && t.constructor.name === 'List' && t.args.length === 1) {
const elemType = t.args[0];
if (pattern.kind === 'list') {
for (const elem of pattern.elements) {
bindPattern(elem, elemType, env, subst);
}
} else {
for (const elem of pattern.head) {
bindPattern(elem, elemType, env, subst);
}
env.set(pattern.spread, t);
}
return null;
}
if (t.kind === 'type-var') return null;
return `Connet match ${prettyPrintType(t)} against list pattern`;
case 'record':
if (t.kind === 'type-record') {
for (const [key, pat] of Object.entries(pattern.fields)) {
@ -219,28 +260,15 @@ function bindPattern(pattern: Pattern, type: TypeAST, env: TypeEnv, subst: Subst
if (field) bindPattern(pat, field.type, env, subst);
}
}
break;
return null;
default:
return null;
}
}
// const int: TypeAST = { kind: 'type-name', name: 'Int' };
// const float: TypeAST = { kind: 'type-name', name: 'Float' };
// const str: TypeAST = { kind: 'type-name', name: 'String' };
// const bool: TypeAST = { kind: 'type-name', name: 'Bool' };
// const tvar = (name: string): TypeAST => ({ kind: 'type-var', name });
// const tfn = (param: TypeAST, result: TypeAST): TypeAST => ({ kind: 'type-function', param, result });
export function typecheck(defs: Definition[]) {
const env: TypeEnv = new Map();
// seed env with builtin types
// env.set('cat', tfn(str, tfn(str, str)));
// env.set('add', tfn(int, tfn(int, int)));
// env.set('sub', tfn(int, tfn(int, int)));
// env.set('mul', tfn(int, tfn(int, int)));
// env.set('div', tfn(int, tfn(int, int)));
// env.set('eq', tfn(tvar('a'), tfn(tvar('a'), bool)));
// Register all annotated defs in env first so they can ref eachother
for (const def of defs) {
if (def.annotation) {