import { Bool, Int } from "./primitives/symbols.js"; import { fnType, lsType } from "./type_registry.js"; import { deepEqual } from "./util.js"; // let nextSymbol = 'a'; // export const makeGeneric = callback => { // const typeParam = Symbol(nextSymbol); // nextSymbol = String.fromCharCode(nextSymbol.charCodeAt(0) + 1); // return { // typeParam, // type: callback(typeParam), // }; // }; import { inspect } from 'node:util'; function pretty(obj) { return inspect(obj, {colors: true}); } export const matchGeneric = ( {typeVars: formalTypeVars, type: formalType}, {typeVars: actualTypeVars, type: actualType}, ) => { if (deepEqual(formalType, actualType)) { return { substitutions: new Map(), typeVars: new Set([ ...actualTypeVars, ...formalTypeVars]), type: actualType, } } if (formalTypeVars.has(formalType)) { // simplest case: substitute formal type param return { substitutions: new Map([[formalType, actualType]]), typeVars: new Set([ ...actualTypeVars, ...formalTypeVars].filter(a => a !== formalType)), type: actualType, }; } if (formalType.in !== undefined) { // function type if (actualType.in === undefined) { throw new Error(`cannot assign ${pretty(actualType)} to ${pretty(formalType)}`); } else { // both are function type const inType = matchGeneric({typeVars: formalTypeVars, type: formalType.in}, {typeVars: actualTypeVars, type: actualType.in}); const outType = matchGeneric({typeVars: formalTypeVars, type: formalType.out}, {typeVars: actualTypeVars, type: actualType.out}); // check for conflicts between 'in' and 'out' subsitutions for (const [typeVar, actual] of inType.substitutions) { if (outType.substitutions.has(typeVar)) { if (!deepEqual(actual, outType.substitutions.get(typeVar))) { throw new Error(`conflicting assignment for ${pretty(typeVar)}: ${pretty(a)}`); } } } // merge substitutions const newSubstitutions = new Map([ ...inType.substitutions, ...outType.substitutions, ]); const newTypeVars = new Set([ ...actualTypeVars, ...formalTypeVars].filter(a => !newSubstitutions.has(a))); return { substitutions: newSubstitutions, typeVars: newTypeVars, type: fnType({in: inType.type, out: outType.type}), }; } } if (formalType.listOf !== undefined) { // list type if (actualType.listOf === undefined) { throw new Error(`cannot assign ${pretty(actualType)} to ${pretty(formalType)}`); } else { // both are list type const elementType = matchGeneric( {typeVars: formalTypeVars, type: formalType.listOf}, {typeVars: actualTypeVars, type: actualType.listOf}); return { substitutions: elementType.substitutions, typeVars: new Set([ ...actualTypeVars, ...formalTypeVars].filter(a => !elementType.substitutions.has(a))), type: lsType(elementType.type), }; } } throw new Error("i don't know what to do :(") }; export const matchConcrete = ({typeVars, type: formalType}, actualType) => { return matchGeneric({typeVars, type: formalType}, {typeVars: new Set(), type: actualType}); }; export const substitute = (type, substitutions) => { if (substitutions.has(type)) { return substitutions.get(type); } if (type.listOf !== undefined) { // list type return lsType(substitute(type.listOf, substitutions)); } if (type.in !== undefined) { // function type return fnType({ in: substitute(type.in, substitutions), out: substitute(type.out, substitutions), }) } throw new Error("i don't know what to do :("); } export const assign = (genFnType, paramType) => { const matchedInType = matchGeneric({ typeVars: genFnType.typeVars, type: genFnType.type.in, }, paramType); const substitutedOutType = substitute(genFnType.type.out, matchedInType.substitutions); return { typeVars: matchedInType.typeVars, type: substitutedOutType, }; } const a = Symbol('a'); const formal = { typeVars: new Set([a]), type: fnType({in: a, out: Int}), }; const actual = fnType({in: lsType(Bool), out: Int}); console.log(matchConcrete(formal, actual)); const b = Symbol('b'); const c = Symbol('c'); const formal2 = { typeVars: new Set([a, b]), type: fnType({in: fnType({in: a, out: a}), out: b}), } const actual2 = { typeVars: new Set([c]), type: fnType({in: fnType({in: Bool, out: Bool}), out: c}), } console.log(matchGeneric(formal2, actual2)); const mapFnType = { typeVars: new Set([a, b]), type: fnType({ in: fnType({in: a, out: b}), out: fnType({in: lsType(a), out: lsType(b)}), }), }; const idFnType = { typeVars: new Set([c]), type: fnType({in: c, out: c}), }; // should be: listOf(c) -> listOf(c) console.log(assign(mapFnType, idFnType));