Acknowledgement
Comment
Originally posted this question on discord, but was advised by @jakebailey to create an issue here in order to get a response from the TS team.
The question is: is it acceptable to type assertion functions with a type predicate whose converse is not true? I know that boolean-returning type guards must be "if and only if" (along the lines of #15048)
// This is NOT a well-formed type predicate
function isLuckyNumber(x: unknown): x is number {
return x === Math.random();
}
// because the false branch is unsound
const three = 3 as number | string;
if (isLuckyNumber(three)) {
three.toExponential(); // safe because of type predicate
} else {
three.toLowerCase(); // throws at runtime!
}
However, is it allowed to do a "one-way" type predicate with asserts? For example like so:
function assertIsLuckyNumber(x: unknown): asserts x is number {
if (x !== Math.random()) {
throw new Error('you are unlucky today!');
}
}
const three = 3 as number | string;
assertIsLuckyNumber(three);
three.toExponential(); // safe because of type predicate
// no "false" branch to abuse!
?
The real-world reason I have for asking this question is that typescript-eslint's no-unnecessary-condition currently flags things such as isNumber(num) as "unnecessary", because a well-formed isNumber() type-guard must return true for any value which is already known to be a number.
The current behavior is to also flag assertNumber(num) as "unnecessary", based on the assumption that the asserting type predicate is also "if and only if", and therefore must never throw for any numeric input. However, if an assertion function such as assertLuckyNumber(num) is considered well-formed, that would not be a valid assumption, as the assertion function could still throw even if the input is known to be of type number. If that's the case, no-unnecessary-condition could never consider an asserting type predicate to be "unnecessary".
I came across a usage in the wild that relies on the "only if" (rather than "if and only if") approach in the typings for node's assert.strictEqual().
So, which one is right? Should tooling assume well-formed type assertion functions are "if-and-only-if" or are "one-way" assertion functions considered well-formed? Is the node type "incorrect" or is the typescript-eslint behavior "incorrect"?
Please advise! Thanks!
PS I rather prefer the interpretation that the assertion functions must be "if and only if" in order to be considered well-formed. The phrase asserts x is number should... assert that x is a number. Not "this function may throw even if x is a number, but if it doesn't throw, then x will definitely be a number".
Acknowledgement
Comment
Originally posted this question on discord, but was advised by @jakebailey to create an issue here in order to get a response from the TS team.
The question is: is it acceptable to type assertion functions with a type predicate whose converse is not true? I know that boolean-returning type guards must be "if and only if" (along the lines of #15048)
However, is it allowed to do a "one-way" type predicate with
asserts? For example like so:?
The real-world reason I have for asking this question is that typescript-eslint's no-unnecessary-condition currently flags things such as
isNumber(num)as "unnecessary", because a well-formedisNumber()type-guard must returntruefor any value which is already known to be anumber.The current behavior is to also flag
assertNumber(num)as "unnecessary", based on the assumption that the asserting type predicate is also "if and only if", and therefore must never throw for any numeric input. However, if an assertion function such asassertLuckyNumber(num)is considered well-formed, that would not be a valid assumption, as the assertion function could still throw even if the input is known to be of typenumber. If that's the case, no-unnecessary-condition could never consider an asserting type predicate to be "unnecessary".I came across a usage in the wild that relies on the "only if" (rather than "if and only if") approach in the typings for node's
assert.strictEqual().So, which one is right? Should tooling assume well-formed type assertion functions are "if-and-only-if" or are "one-way" assertion functions considered well-formed? Is the node type "incorrect" or is the typescript-eslint behavior "incorrect"?
Please advise! Thanks!
PS I rather prefer the interpretation that the assertion functions must be "if and only if" in order to be considered well-formed. The phrase
asserts x is numbershould... assert thatxis a number. Not "this function may throw even if x is a number, but if it doesn't throw, then x will definitely be a number".