I am interested in narrowing types based on the type of a single field. However, TypeScript doesn't seem to be using the type predicate to narrow the type of the other parameters in the type like it does when using primitive operators directly in the if
statement. Is there anything I can do to get the type narrowing to work correctly here?
export function isTrue(input: boolean | undefined | null): input is true {
return input === true;
}
type Refine =
| {
b: true;
c: 'bIsTrue';
}
| {
b: undefined;
c: 'bIsUndefined';
}
| {
b: false;
c: 'bIsFalse';
};
export function example() {
const example = (null as unknown) as Refine;
if (example.b === true) {
example.b; // Type is: true
example.c; // Type is: 'bIsTrue'
}
if (isTrue(example.b)) {
example.b; // Type is: true
example.c; // Type is: 'bIsTrue' | 'bIsUndefined' | 'bIsFalse'
}
}
CodePudding user response:
One possibility is to perform the narrowing on the whole object, then access properties of it:
function hasTrueBProperty<T extends { b: unknown }>(object: T): object is T & { b: true } {
return object.b === true;
}
declare const refine: Refine;
if (hasTrueBProperty(refine)) {
refine.b; // Type is: true
refine.c; // Type is: 'bIsTrue'
}
To pass the property to test as an argument, use another generic:
function hasTrueProperty<P extends PropertyKey, T extends Record<P, unknown>>(object: T, prop: P): object is T & Record<P, true> {
return object[prop] === true;
}
declare const refine: Refine;
if (hasTrueProperty(refine, 'b')) {
refine.b; // Type is: true
refine.c; // Type is: 'bIsTrue'
}
CodePudding user response:
Unfortunately, the typescript team has officially stated that they're not willing to address this use case, for performance reasons. As an alternative, you could introduce types and typeguards here. E.g.:
interface One {
b: true;
c: "bIsTrue";
}
interface Two {
b: undefined;
c: "bIsUndefined";
}
interface Three {
b: false;
c: "bIsFalse";
}
type Refine = One | Two | Three;
function isOne(refine: Refine): refine is One {
return refine.b === true;
}
function example() {
const example = (null as unknown) as Refine;
if (example.b === true) {
example.b; // Type is: true
example.c; // Type is: 'bIsTrue'
}
if (isOne(example)) {
example.b; // Type is: true
example.c; // Type is: 'bIsTrue'
}
}