You can use overloads for each possible valid combination:
enum ChartType {
ChartTypeA,
ChartTypeB,
ChartTypeC
}
enum DataType {
DataTypeA,
DataTypeB,
DataTypeC
}
function generateChart(chartType: ChartType.ChartTypeA, dataType: DataType.DataTypeA)
function generateChart(chartType: ChartType.ChartTypeC, dataType: DataType.DataTypeC)
function generateChart(chartType: ChartType.ChartTypeB, dataType: DataType.DataTypeB)
function generateChart(chartType: ChartType, dataType: DataType) { // Implementation signature
}
generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error
Or we can use a mapping type to cut down the ceremony a bit:
interface EnuMap {
[ChartType.ChartTypeA]: DataType.DataTypeA,
[ChartType.ChartTypeB]: DataType.DataTypeB,
[ChartType.ChartTypeC]: DataType.DataTypeC,
}
function generateChart<T extends ChartType>(chartType: T, dataType: EnuMap[T])
function generateChart(chartType: ChartType, dataType: DataType) { // Implementation signature
}
generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error
Note If we use an interface for the mapping type, the interface can be extended as needed by a plugin for example if it ads support for a new combination of types.
Edit
If most combinations are possible and only a few should be excluded we could use a different approach. First create a type that contains all possible combinations of parameters and the use Exclude
to take out the imposible combinations:
function generateChart<T extends Excluded>(...a: T)
function generateChart(chartType: ChartType, dataType: DataType) { // Implementation signature
}
type AllCombinations = {
[C in ChartType]: {
[D in DataType]: [C, D]
}
}[ChartType][DataType]
// Exclude unwanted combinations
type Excluded = Exclude<AllCombinations, [ChartType.ChartTypeA, DataType.DataTypeC]>;
generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeB, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error
We lose a bit of expresivness with this approach in parameter names and the overloads the compiler suggests (just a code completion thing, it works as expected)
A solution that plays nicer with intelisense and keeps parameter names could be constructed using UnionToIntersection
from here. We first create a union of all possible signatures and then we use UnionToIntersection
to create a function with all overloads.
type AllCombinations = {
[C in ChartType]: {
[D in DataType]: [C, D]
}
}[ChartType][DataType]
type Excluded = Exclude<AllCombinations, [ChartType.ChartTypeA, DataType.DataTypeC]>;
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type SignatureHelper<T> = T extends [infer C, infer D] ? (chartType: C, dataType: D) => void : never;
type GenerateChartType = UnionToIntersection<SignatureHelper<Excluded>>
const generateChart:GenerateChartType = (chartType: ChartType, dataType: DataType) => { // Implementation signature
}
generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeB, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error