I have assertIsDefined
type guard with expected value.
I need to type narrowing to expected value with type suggestions.
| unknown
to generic type, and suggestions is not showing with it. | unknown
in generic type, suggestions are showing, but type narrowing is not working.What should I do with my type guard to make it work like a shine?
{
const value_number1 = 123 as 3 | 123 | 777 | undefined;
// ^?
// 😪 INCURRECT BEHAVIOUR: remove '123' from ', 123' and 'Ctrl+Space' to no suggestions showed 😪
assertIsDefined_withDetailedType(value_number1, 123);
// 🌈 CURRECT BEHAVIOUR: calculated type of value_number1 is 123 🌈
console.log(value_number1);// value_number1: 123
// ^?
}
{
const value_number2 = 123 as 3 | 123 | 777 | undefined;
// ^?
// 🌈 CURRECT BEHAVIOUR: remove '123' from ', 123' and 'Ctrl+Space' to show SUGGESTIONS: should be [ 123, 3, 777 ] 🌈
assertIsDefined_withSuggestions(value_number2, 123);
// 😪 INCURRECT BEHAVIOUR: calculated type of value_number2 is 3 | 123 | 777 😪
console.log(value_number2);// value_number2: 3 | 123 | 777
// ^?
}
function assertIsDefined_withDetailedType<T>(value: T | unknown | null | undefined, expected: T): asserts value is T {
if (value == null || expected !== value) {
throw new TypeError(`value should be defined and equal expected value`);
}
}
function assertIsDefined_withSuggestions<T>(value: T | null | undefined, expected: T): asserts value is T {
if (value == null || expected !== value) {
throw new TypeError(`value should be defined and equal expected value`);
}
}
I expect:
{
const value_number2 = 123 as 3 | 123 | 777 | undefined;
// ^?
// 🌈 CURRECT BEHAVIOUR: remove '123' from ', 123' and 'Ctrl+Space' to show SUGGESTIONS: should be [ 123, 3, 777 ] 🌈
assertIsDefined_withAllWeWant(value_number2, 123);
// 🌈 CURRECT BEHAVIOUR: calculated type of value_number2 is 123 🌈
console.log(value_number2);// value_number2: 123
// ^?
}
// Some gorgeous type guard
function assertIsDefined_withAllWeWant(value: T/*code here?*/, expected: T): asserts value is T {
if (value == null || expected !== value) {
throw new TypeError(`value should be defined and equal expected value`);
}
}
I would write assertIsDefined()
to be generic in both the type T
of value
and the type U
of expected
, where U
is constrainted to both T
(so it's a narrowing) and {}
(so it's NonNullable
; an intersection with the empty object type only prohibits null
and undefined
):
function assertIsDefined<T, U extends T & {}>(
value: T, expected: U): asserts value is U {
if (value == null || expected !== value) {
throw new TypeError(`value should be defined and equal expected value`);
}
}
Now you can only specify an expected
as some non-nullish subtype of the type of value
, and in TS5.4 and above you get nice autosuggestions as well:
const value = 123 as 3 | 123 | 777 | undefined;
// ^? const value: 3 | 123 | 777 | undefined;
assertIsDefined(value, 123); // <-- autosuggest 3 | 123 | 777
console.log(value);
// ^? const value: 123