I have established a mapping for a discriminated union consisting of different types, each linked to a corresponding function that uses a member of the union as a parameter:
export interface Truncate {
type: 'truncate'
maxLength: number
}
export interface Append {
type: 'append'
suffix: string
}
export type Applicable = Truncate | Append
const applicableMap: { [K in Applicable['type']]: (base: string, applicable: Extract<Applicable, { type: K }>) => string } = {
'truncate': (s, applicable) => {
return s.slice(0, applicable.maxLength)
},
'append': (s, applicable) => {
return s + applicable.suffix
}
}
Everything seems correct so far; TypeScript accurately deduces the types of the applicable
argument in each function and performs comprehensive checking on the keys, preventing me from unintentionally omitting a map entry when adding a new member.
Now, I have an apply
function that selects a function from the map and attempts to execute it:
function apply(s: string, applicable: Applicable): string {
return applicableMap[applicable.type](s, applicable)
}
However, this fails to compile due to the following error message:
Argument of type 'Applicable' is not assignable to parameter of type 'never'.
The intersection 'Add & Append' was reduced to 'never' because property 'type' has conflicting types in some constituents.
Type 'Add' is not assignable to type 'never'.ts(2345)
This outcome surprises me because I anticipate TypeScript to recognize that this call will always be valid.
Thus far, the only workaround I've discovered is to make the call as
applicableMap[applicable.type](s, applicable as any)
, but I consider using any
as a temporary fix.
Which leads me to the question:
Is this limitation simply a current constraint of TypeScript, or is there a method to adjust the types while still maintaining the type inference and exhaustive checking?