Type Aliases!
This commit is contained in:
parent
32815301fa
commit
25c3ac5c0d
3 changed files with 47 additions and 2 deletions
|
|
@ -173,6 +173,7 @@ export type TypeDefinition = {
|
||||||
name: string
|
name: string
|
||||||
params: string[]
|
params: string[]
|
||||||
constructors: TypeConstructor[]
|
constructors: TypeConstructor[]
|
||||||
|
alias?: TypeAST
|
||||||
line?: number
|
line?: number
|
||||||
column?: number
|
column?: number
|
||||||
start?: number
|
start?: number
|
||||||
|
|
|
||||||
|
|
@ -715,6 +715,13 @@ export class Parser {
|
||||||
|
|
||||||
this.expect('equals');
|
this.expect('equals');
|
||||||
|
|
||||||
|
// If next token isn't a type-ident, it's a type alias
|
||||||
|
if (this.current().kind !== 'type-ident') {
|
||||||
|
const alias = this.parseType();
|
||||||
|
if (this.current().kind === 'semicolon') this.advance();
|
||||||
|
return { kind: 'type-definition', name, params, constructors: [], alias, ...this.getPos(nameToken) };
|
||||||
|
}
|
||||||
|
|
||||||
// Parse constructors separated by |
|
// Parse constructors separated by |
|
||||||
const constructors: TypeConstructor[] = [];
|
const constructors: TypeConstructor[] = [];
|
||||||
constructors.push(this.parseTypeConstructor());
|
constructors.push(this.parseTypeConstructor());
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ function applySubst(type: TypeAST, subst: Subst): TypeAST {
|
||||||
}
|
}
|
||||||
|
|
||||||
function unify(t1: TypeAST, t2: TypeAST, subst: Subst): string | null {
|
function unify(t1: TypeAST, t2: TypeAST, subst: Subst): string | null {
|
||||||
const a = applySubst(t1, subst);
|
const a = resolveAlias(applySubst(t1, subst));
|
||||||
const b = applySubst(t2, subst);
|
const b = resolveAlias(applySubst(t2, subst));
|
||||||
|
|
||||||
// Same type name
|
// Same type name
|
||||||
if (a.kind === 'type-name' && b.kind === 'type-name' && a.name === b.name) return null;
|
if (a.kind === 'type-name' && b.kind === 'type-name' && a.name === b.name) return null;
|
||||||
|
|
@ -376,6 +376,7 @@ let typeConstructors = new Map<string, string[]>();
|
||||||
|
|
||||||
export function typecheck(defs: Definition[], typeDefs: TypeDefinition[] = [], classDefs: ClassDefinition[] = [], instances: InstanceDeclaration[] = []) {
|
export function typecheck(defs: Definition[], typeDefs: TypeDefinition[] = [], classDefs: ClassDefinition[] = [], instances: InstanceDeclaration[] = []) {
|
||||||
const env: TypeEnv = new Map();
|
const env: TypeEnv = new Map();
|
||||||
|
const typeAliases = new Map<string, { params: string[], type: TypeAST }>();
|
||||||
|
|
||||||
// Register instances as a lookup: className -> Set of type names
|
// Register instances as a lookup: className -> Set of type names
|
||||||
const instanceMap = new Map<string, Set<string>>();
|
const instanceMap = new Map<string, Set<string>>();
|
||||||
|
|
@ -395,6 +396,11 @@ export function typecheck(defs: Definition[], typeDefs: TypeDefinition[] = [], c
|
||||||
|
|
||||||
// Register constructors
|
// Register constructors
|
||||||
for (const td of typeDefs) {
|
for (const td of typeDefs) {
|
||||||
|
if (td.alias) {
|
||||||
|
typeAliases.set(td.name, { params: td.params, type: td.alias });
|
||||||
|
continue; // don't register constructors for aliases
|
||||||
|
}
|
||||||
|
|
||||||
typeConstructors.set(td.name, td.constructors.map(c => c.name));
|
typeConstructors.set(td.name, td.constructors.map(c => c.name));
|
||||||
|
|
||||||
const resultType: TypeAST = td.params.length === 0
|
const resultType: TypeAST = td.params.length === 0
|
||||||
|
|
@ -414,6 +420,8 @@ export function typecheck(defs: Definition[], typeDefs: TypeDefinition[] = [], c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moduleTypeAliases = typeAliases;
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
@ -543,3 +551,32 @@ function checkExhaustiveness(scrutType: TypeAST, cases: { pattern: Pattern }[],
|
||||||
warn(`Non-exhaustive match, missing: ${missing.join(', ')}`, expr);
|
warn(`Non-exhaustive match, missing: ${missing.join(', ')}`, expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let moduleTypeAliases = new Map<string, { params: string[], type: TypeAST }>();
|
||||||
|
|
||||||
|
function resolveAlias(t: TypeAST): TypeAST {
|
||||||
|
if (t.kind === 'type-name' && moduleTypeAliases.has(t.name)) {
|
||||||
|
return moduleTypeAliases.get(t.name)!.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.kind === 'type-apply' && t.constructor.kind === 'type-name' && moduleTypeAliases.has(t.constructor.name)) {
|
||||||
|
const alias = moduleTypeAliases.get(t.constructor.name);
|
||||||
|
// Substitute params: alias.params[i] -> t.args[i]
|
||||||
|
let result = alias.type;
|
||||||
|
for (let i = 0; i < alias.params.length && i < t.args.length; i++) {
|
||||||
|
result = substituteTypeVar(alias.params[i], t.args[i], result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function substituteTypeVar(name: string, replacement: TypeAST, type: TypeAST): TypeAST {
|
||||||
|
switch (type.kind) {
|
||||||
|
case 'type-var': return type.name === name ? replacement : type;
|
||||||
|
case 'type-name': return type;
|
||||||
|
case 'type-function': return { kind: 'type-function', param: substituteTypeVar(name, replacement, type.param), result: substituteTypeVar(name, replacement, type.result) };
|
||||||
|
case 'type-apply': return { kind: 'type-apply', constructor: substituteTypeVar(name, replacement, type.constructor), args: type.args.map(a => substituteTypeVar(name, replacement, a)) };
|
||||||
|
case 'type-record': return { kind: 'type-record', fields: type.fields.map(f => ({ name: f.name, type: substituteTypeVar(name, replacement, f.type) })) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue