add disjoint union type
This commit is contained in:
parent
d8bd69528b
commit
272ffd6c58
1 changed files with 99 additions and 0 deletions
99
src/union.js
Normal file
99
src/union.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @template R
|
||||
* @typedef {(...args: any[]) => R} Fn
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template R
|
||||
* @typedef {() => R} EmptyFn
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template R
|
||||
* @callback match
|
||||
* @param {Record<string, Fn<R>> & Partial<Record<'_', EmptyFn<R>>>} pattern
|
||||
* @returns {R}
|
||||
*
|
||||
* @throws MatchError
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Variant
|
||||
* @property {match<any>} match
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(...values: any[]) => Variant} VariantConstructor
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {PropertyKey[]} Variant
|
||||
* @typedef {{ [key in Variant[number]]: (...values: any) => Variant }} Variants
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {PropertyKey} T
|
||||
* @template {PropertyKey[]} U
|
||||
* @typedef {{ is: typeof is } & Variants<U>} Union
|
||||
*/
|
||||
|
||||
const Tag = Symbol('Tag')
|
||||
|
||||
class MatchError extends Error {
|
||||
/** @param {PropertyKey} name */
|
||||
constructor(name) {
|
||||
super(`unmatched arm in match: ${name.toString()}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyKey} variant
|
||||
* @param {PropertyKey} variant
|
||||
* @returns {(...values: any[]) => Variant}
|
||||
*/
|
||||
const Variant = (type, variant) => (...values) => ({
|
||||
[Tag]: type,
|
||||
match(pattern) {
|
||||
if (variant in pattern) {
|
||||
// NOTE: this is a workaround for typescript not recognizing
|
||||
// that objects can be indexed with symbols
|
||||
return pattern[ /** @type {string} */ (variant)](...values)
|
||||
} else if ('_' in pattern) {
|
||||
return pattern._()
|
||||
} else {
|
||||
throw new MatchError(variant)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @template {object} T
|
||||
* @typedef {T[keyof T]} Tuple
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T, U
|
||||
* @this {Union<T, U>}
|
||||
* @param {any} other
|
||||
* @returns {other is Variant<U>}
|
||||
*/
|
||||
function is(other) {
|
||||
return Object.hasOwn(other, Tag) && other[Tag] === this[Tag]
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {PropertyKey} const T
|
||||
* @template {Array<PropertyKey>} const U
|
||||
* @param {T} typeName
|
||||
* @param {...U} variantNames
|
||||
* @returns {Union<T, U>}
|
||||
*/
|
||||
export const Union = (typeName, variantNames) => {
|
||||
const tag = { [Tag]: typeName, is }
|
||||
const variants = Object.fromEntries(variantNames.map(v => [v, Variant(typeName, v)]))
|
||||
const result = Object.assign(tag, variants)
|
||||
|
||||
|
||||
return /** @type {Union<T, U>} */ (result)
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue