I take it from the question that if vehicleType is Pickup, trailer is a required argument.
If so, the issue is that TypeScript doesn't know that; as far as it knows, vehicle(VehicleType.Pickup, someVehicle) (with no trailer) is perfectly valid.
You can tell it the specific combinations of parameters that are valid using function overloads; it doesn't completely solve the problem, but it gets us close:
function vehicle(vehicleType: VehicleType.Car, vehicle: Vehicle): string;
function vehicle(vehicleType: VehicleType.Pickup, vehicle: Vehicle, trailer: Trailer): string;
function vehicle(vehicleType: VehicleType, vehicle: Vehicle, trailer?: Trailer): string {
switch (vehicleType) {
case VehicleType.Car:
return `${vehicle.length} ${vehicle.weight}`;
case VehicleType.Pickup:
assertNotNullish(trailer);
return `${vehicle.length + trailer.length} ${vehicle.weight + trailer.weight}`;
}
}
Only the first two of those are public signatures; the third one, with the implementation, is just the implementation signature and isn't seen by any other code.
Now the problem is that in the implemenation code, it's still complaining that trailer may be undefined, even though you know (from the overload signatures) that it won't be. There are two ways to solve that:
- The not-nullish assertion operator, which is a postfix
!
- An explicit assertion
Here's the non-nullish version, note the ! before . when using trailer:
function vehicle(vehicleType: VehicleType.Car, vehicle: Vehicle): string;
function vehicle(vehicleType: VehicleType.Pickup, vehicle: Vehicle, trailer: Trailer): string;
function vehicle(vehicleType: VehicleType, vehicle: Vehicle, trailer?: Trailer): string {
switch (vehicleType) {
case VehicleType.Car:
return `${vehicle.length} ${vehicle.weight}`;
case VehicleType.Pickup:
return `${vehicle.length + trailer!.length} ${vehicle.weight + trailer!.weight}`;
}
}
Playground link
That assures TypeScript that we know trailer won't be undefined. (If we're wrong, we'll get an error at runtime along the lines of "Cannot read property 'lenght' of null").
That works, but many people don't like that kind of subtle assertion in the code (including me). Another option is to have a utility function lying around that you can use to explicitly document your assertion something won't be nullish:
function assertNotNullish<T>(value: T | null | undefined): asserts value is T {
if (value ?? null === null) {
throw new Error(`Got null/undefined value where non-null/non-undefined value expected`);
}
}
That function tells TypeScript that if it completes without throwing an exception, the value we passed to it isn't null or undefined. Then we use it like this:
function vehicle(vehicleType: VehicleType.Car, vehicle: Vehicle): string;
function vehicle(vehicleType: VehicleType.Pickup, vehicle: Vehicle, trailer: Trailer): string;
function vehicle(vehicleType: VehicleType, vehicle: Vehicle, trailer?: Trailer): string {
switch (vehicleType) {
case VehicleType.Car:
return `${vehicle.length} ${vehicle.weight}`;
case VehicleType.Pickup:
assertNotNullish(trailer);
return `${vehicle.length + trailer.length} ${vehicle.weight + trailer.weight}`;
}
}
Playground link