This code (which is weird, but I did it only to pose this question):
interface Vehicle<C> {
id: string
name: string
config: C | null
}
interface CarConfig {
leatherSeats: boolean
}
const Car: Vehicle<CarConfig> = {
id: "car",
name: "car",
config: null
}
interface TruckConfig {
numberOfWheels: number
}
const Truck: Vehicle<TruckConfig> = {
id: "truck",
name: "truck",
config: null
}
function getVehicleConfig<C>(vehicle: Vehicle<C>): C | null {
if (vehicle === Car) {
return vehicle.config
}
if (vehicle === Truck) {
return vehicle.config
}
throw new Error("Unknown vehicle")
}
Compiles with this error:
src/explain.ts:28:9 - error TS2367: This condition will always return 'false' since the types 'Vehicle<C>' and 'Vehicle<CarConfig>' have no overlap.
The expectation here is that (due to constraints in another part of the software) vehicle in getVehicleConfig will always be either the Car object or the Truck object, and getVehicleConfig will return the config associated with the instance.
What is the right way to express getVehicleConfig in a type safe manner?
>Solution :
The way to go would be a discriminated union :
type VehiculeType= 'Car' | 'Truck';
interface Vehicle<C> {
id: string
name: string
config: C | null
type: VehiculeType;
}
interface CarConfig {
leatherSeats: boolean
}
const Car: Vehicle<CarConfig> = {
type: 'Car',
id: "car",
name: "car",
config: null
}
interface TruckConfig {
numberOfWheels: number
}
const Truck: Vehicle<TruckConfig> = {
type: 'Truck',
id: "truck",
name: "truck",
config: null
}
function getVehicleConfig<C>(vehicle: Vehicle<C>): C | null {
if (vehicle.type === 'Car') {
return vehicle.config
}
if (vehicle.type === 'Truck') {
return vehicle.config
}
throw new Error("Unknown vehicle")
}