Type Aliases!

This commit is contained in:
Dustin Swan 2026-04-12 20:26:16 -06:00
parent 32815301fa
commit 25c3ac5c0d
No known key found for this signature in database
GPG key ID: 30D46587E2100467
3 changed files with 47 additions and 2 deletions

View file

@ -173,6 +173,7 @@ export type TypeDefinition = {
name: string
params: string[]
constructors: TypeConstructor[]
alias?: TypeAST
line?: number
column?: number
start?: number

View file

@ -715,6 +715,13 @@ export class Parser {
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 |
const constructors: TypeConstructor[] = [];
constructors.push(this.parseTypeConstructor());

View file

@ -26,8 +26,8 @@ function applySubst(type: TypeAST, subst: Subst): TypeAST {
}
function unify(t1: TypeAST, t2: TypeAST, subst: Subst): string | null {
const a = applySubst(t1, subst);
const b = applySubst(t2, subst);
const a = resolveAlias(applySubst(t1, subst));
const b = resolveAlias(applySubst(t2, subst));
// Same type name
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[] = []) {
const env: TypeEnv = new Map();
const typeAliases = new Map<string, { params: string[], type: TypeAST }>();
// Register instances as a lookup: className -> Set of type names
const instanceMap = new Map<string, Set<string>>();
@ -395,6 +396,11 @@ export function typecheck(defs: Definition[], typeDefs: TypeDefinition[] = [], c
// Register constructors
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));
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
for (const def of defs) {
if (def.annotation) {
@ -543,3 +551,32 @@ function checkExhaustiveness(scrutType: TypeAST, cases: { pattern: Pattern }[],
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) })) };
}
}