diff --git a/src/cg/01-stdlib.cg b/src/cg/01-stdlib.cg index 90bb3ba..fa8f8b3 100644 --- a/src/cg/01-stdlib.cg +++ b/src/cg/01-stdlib.cg @@ -17,6 +17,7 @@ Maybe a = None | Some a; # | [n, [x, ...xs]] \ nth (n - 1) xs; 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]; diff --git a/src/typechecker.ts b/src/typechecker.ts index 5f02852..49d349a 100644 --- a/src/typechecker.ts +++ b/src/typechecker.ts @@ -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) {