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

@ -17,6 +17,7 @@ Maybe a = None | Some a;
# | [n, [x, ...xs]] \ nth (n - 1) xs; # | [n, [x, ...xs]] \ nth (n - 1) xs;
map : (a \ b) \ List a \ List b = f list \ list map : (a \ b) \ List a \ List b = f list \ list
# map : (a \ b) \ Int \ List b = f list \ list
| [] \ [] | [] \ []
| [x, ...xs] \ [f x, ...map f xs]; | [x, ...xs] \ [f x, ...map f xs];

View file

@ -88,6 +88,15 @@ function infer(expr: AST, env: TypeEnv, subst: Subst): TypeAST | null {
return t ? applySubst(t, subst) : 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': { case 'record': {
const fields: { name: string, type: TypeAST }[] = []; const fields: { name: string, type: TypeAST }[] = [];
for (const entry of expr.entries) { 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); const scrutType = infer(expr.expr, env, subst);
for (const c of expr.cases) { for (const c of expr.cases) {
const caseEnv = new Map(env); 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); const err = check(c.result, expected, caseEnv, subst);
if (err) warn(err, c.result); 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); 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 // Fallback: infer and unify
const inferred = infer(expr, env, subst); const inferred = infer(expr, env, subst);
if (!inferred) return null; // Can't infer, skip silently if (!inferred) return null; // Can't infer, skip silently
@ -199,19 +222,37 @@ function warn(msg: string, expr: AST) {
console.warn(`TypeError${loc}: ${msg}`); 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); const t = applySubst(type, subst);
switch (pattern.kind) { switch (pattern.kind) {
case 'var': case 'var':
env.set(pattern.name, t); env.set(pattern.name, t);
break; return null;
case 'wildcard':
case 'literal':
return null;
case 'constructor': case 'constructor':
// TODO: look up ctor arg types // TODO: look up ctor arg types
break; break;
case 'list': case 'list':
case 'list-spread': case 'list-spread':
// TODO: bind element types if (t.kind === 'type-apply' && t.constructor.kind === 'type-name' && t.constructor.name === 'List' && t.args.length === 1) {
break; 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': case 'record':
if (t.kind === 'type-record') { if (t.kind === 'type-record') {
for (const [key, pat] of Object.entries(pattern.fields)) { 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); 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[]) { export function typecheck(defs: Definition[]) {
const env: TypeEnv = new Map(); 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 // Register all annotated defs in env first so they can ref eachother
for (const def of defs) { for (const def of defs) {
if (def.annotation) { if (def.annotation) {