UNPKG

@markandrus/effect-derive

Version:

Derive Covariant (Functor), Foldable, and Traversable instances, as well as base functors, for algebraic data types (ADTs)

103 lines (100 loc) 4.44 kB
import { Node } from 'ts-morph'; import { deriveCovariant } from './deriveCovariant'; import { deriveFoldable } from './deriveFoldable'; import { deriveTraversable } from './deriveTraversable'; import { deriveTypeLambda } from './deriveTypeLambda'; import { OutFile } from './OutFile'; export function deriveBaseFunctor(inFilePath, forType, discriminator, registries, node, extrasToDerive) { const outFile = new OutFile(); const tyParams = node.getTypeParameters(); if (tyParams.length > 2) { throw new Error('At most 2 type parameters are supported when deriving baase functor, due to limitations in effect\'s HKT encoding'); } const tA = tyParams.length > 0 ? 'A' : 'never'; const tE = tyParams.length > 1 ? 'E' : 'never'; const freeTyParams = tA !== 'never' || tE !== 'never' ? `<${tE !== 'never' ? `${tE}, ` : ''}${tA}>` : ''; const tyParamsSet = new Set(tyParams.map(tyParam => tyParam.getText())); let newTyParamName = 'X'; for (let i = 0; tyParamsSet.has(newTyParamName); i++) { newTyParamName = `X${i + 1}`; } // NOTE(mroberts): Do this before mutating `node`. if (!registries.typeLambda.has(forType)) { outFile.merge(deriveTypeLambda(inFilePath, forType, registries.typeLambda, node)); } node.insertTypeParameter(tyParams.length, { name: newTyParamName }); const tyNode = node.getTypeNodeOrThrow(); let tyNodes = [tyNode]; if (Node.isUnionTypeNode(tyNode)) { if (discriminator == null) { throw new Error('--discriminator is required for union types'); } tyNodes = tyNode.getTypeNodes(); } else if (!Node.isTypeLiteral(tyNode)) { throw new Error(`Type alias "${forType}" must be a union or type literal`); } handleTypeNodes(forType, newTyParamName, tyNodes); node.rename(forType + 'F', {}); outFile.addDeclarations(node.print() + '\n\n'); outFile .merge(deriveTypeLambda(undefined, forType + 'F', registries.typeLambda, node)) .merge(deriveCovariant(undefined, forType + 'F', discriminator, registries, node)); if (extrasToDerive.has('Foldable')) { outFile.merge(deriveFoldable(undefined, forType + 'F', discriminator, registries, node)); } if (extrasToDerive.has('Traversable')) { outFile.merge(deriveTraversable(undefined, forType + 'F', discriminator, registries, node)); } // TODO(mroberts): We should publish these, so that we don't have to use relative // paths, which won't work in other projects. outFile .addLocalImport('../Recursive', 'Recursive', 'R', true) .addLocalImport('../Corecursive', 'Corecursive', 'C', true); if (extrasToDerive.has('Recursive')) { outFile .addLocalImport('../Recursive', 'Recursive', 'R', true) .addDeclarations(`\ export const Recursive: ${freeTyParams !== '' ? `${freeTyParams}() => ` : ''}R<${forType}TypeLambda, ${forType}FTypeLambda, never, never, ${tE}, ${tA}, never, ${tE}, ${tA}> = ${freeTyParams !== '' ? '() => (' : ''}{ F: Covariant, project: t => t }${freeTyParams !== '' ? ')' : ''} `); } if (extrasToDerive.has('Corecursive')) { outFile .addLocalImport('../Corecursive', 'Corecursive', 'C', true) .addDeclarations(`\ export const Corecursive: ${freeTyParams !== '' ? `${freeTyParams}() => ` : ''}C<${forType}TypeLambda, ${forType}FTypeLambda, never, never, ${tE}, ${tA}, never, ${tE}, ${tA}> = ${freeTyParams !== '' ? '() => (' : ''}{ F: Covariant, embed: t => t }${freeTyParams !== '' ? ')' : ''} `); } return outFile; } function handleTypeNodes(forType, tyParam, tyNodes) { for (const tyNode of tyNodes) { handleTypeNode(forType, tyParam, tyNode); } } function handleTypeNode(forType, tyParam, tyNode) { if (!Node.isTypeLiteral(tyNode)) { throw new Error(`Every member of the union type "${forType}" must be a TypeLiteral`); } for (const member of tyNode.getMembers()) { if (!Node.isPropertySignature(member)) { throw new Error(`Expected a PropertySignature; got ${member.getKindName()}`); } const memberValue = member.getTypeNodeOrThrow(); if (Node.isTypeReference(memberValue)) { if (memberValue.getTypeName().getText() === forType) { memberValue.replaceWithText(tyParam); } } } }