Yes, you can use overload signatures to achieve exactly what you want:
type Fruit = "apple" | "orange"
function doSomething(foo: "apple"): string;
function doSomething(foo: "orange"): string[];
function doSomething(foo: Fruit): string | string[]
{
if (foo == "apple") return "hello";
else return ["hello", "world"];
}
let orange: string[] = doSomething("orange");
let apple: string = doSomething("apple");
Trying to assign doSomething("apple")
to orange
would yield a compile-time type-error:
let orange: string[] = doSomething("apple");
// ^^^^^^
// type 'string' is not assignable to type 'string[]'
Live demo on TypeScript Playground
It is important to note that determining which overload signature was used must always be done in the function implementation manually, and the function implementation must support all overload signatures.
There are no separate implementations per overload in TypeScript as there are in, say, C#. As such, I find it a good practice to reinforce TypeScript type-checks at runtime, for example:
switch (foo) {
case "apple":
return "hello";
case "orange":
return ["hello", "world"];
default:
throw new TypeError("Invalid string value.");
}
test
variable type should bestring[]|string
– Aleksey L."orange"
as the argument todoSomething
always yields astring[]
, then it is correct fortest
to have that type as well. You need to use overloading. – John WeiszdoSomething
signature clearly states that the return type isstring[]|string
which is not the case with overloading where you just specifying return type according to specific input – Aleksey L."orange" => string[]
is the signature that actually represents the runtime behaviour, and not"orange" => string | string[]
. – John Weisz