I have a function that is intended to either return a value of IDBValidKey
or something converted to IDBValidKey
. If I write the function using the ternary operator it works fine but it causes a compiler error if I write it as an if-else statement:
interface IDBValidKeyConvertible<TConverted extends IDBValidKey> {
convertToIDBValidKey: () => TConverted;
}
function isIDBValidKeyConvertible<TConvertedDBValidKey extends IDBValidKey>(object: unknown): object is IDBValidKeyConvertible<TConvertedDBValidKey> {
return typeof((object as IDBValidKeyConvertible<TConvertedDBValidKey>).convertToIDBValidKey) === "function";
}
type IDBValidKeyOrConverted<TKey> = TKey extends IDBValidKeyConvertible<infer TConvertedKey> ? TConvertedKey : TKey;
function getKeyOrConvertedKey<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey> {
if (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input)) {
return input.convertToIDBValidKey();
} else {
return input;
}
}
function getKeyOrConvertedKeyTernary<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey> {
return (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input)) ? input.convertToIDBValidKey() : input;
}
getKeyOrConvertedKeyTernary
produces no errors but the else
block of getKeyOrConvertedKey
yields this error:
Type 'TKey' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
Type 'string | number | Date | ArrayBufferView | ArrayBuffer | IDBArrayKey | IDBValidKeyConvertible<any>' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
Type 'string' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
Aren't the ternary operator and the if-else statement equivalent?
Thanks!
Why does using an 'if-else' statement produce a TypeScript compiler error when a seemingly identical ternary operator construct does not?
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.
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.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With