Why does using an 'if-else' statement produce a TypeScript compiler error when a seemingly identical ternary operator construct does not?
Why does using an 'if-else' statement produce a TypeScript compiler error when a seemingly identical ternary operator construct does not?
Short Answer
TypeScript sees an if-else
as a statement with multiple expressions that each have independent types. TypeScript sees a ternary as an expression with a union type of its true and false sides. Sometimes that union type becomes wide enough for the compiler not to complain.
Detailed Answer
Aren't the ternary operator and the if-else statement equivalent?
Not quite.
The difference stems from a ternary being an expression. There is a conversation here where Ryan Cavanaugh explains the difference between a ternary and an if/else statement. The take home is that the type of a ternary expression is a union of its true
and false
results.
For your particular situation, the type of your ternary expression is any
. That is why the compiler does not complain. Your ternary is a union of the input
type and the input.convert()
return type. At compile time, the input
type extends Container<any>
; therefore the input.convert()
return type is any
. Since a union with any
is any
, the type of your ternary is, well, any
.
A quick solution for you is to change any
to unknown
in <TKey extends IDBValidKey | IDBValidKeyConvertible<any>
. That will make both the if-else and the ternary produce a compiler error.
Simplified Example
Here is a playground link with a simplified reproduction of your question. Try changing the any
to unknown
to see how the compiler responds.
interface Container<TValue> {
value: TValue;
}
declare function hasValue<TResult>(
object: unknown
): object is Container<TResult>;
// Change any to unknown.
const funcIfElse = <T extends Container<any>>(input: T): string => {
if (hasValue<string>(input)) {
return input.value;
}
return input;
};
// Change any to unknown.
const funcTernary = <T extends Container<any>>(input: T): string =>
hasValue<string>(input)
? input.value
: input;