Fixing some unchecked errors
This commit is contained in:
parent
f272ffaca2
commit
b97eb52c21
2 changed files with 50 additions and 21 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue