Jesse Carter
Jesse Carter

Reputation: 21157

User-Defined Type Guard Results in Intersection Type

I'm playing around with Typescript and was trying to see if I could create the concept of an Either type to represent success and failure paths. I've created a user-defined type guard to explicitly narrow the types of success/failure through the use of a Symbol. I can't figure out why the value of either in the isFailure branch is shown as being the intersection type of Data & CustomError as opposed to just CustomError.

const FailureSymbol = Symbol('Failure');

interface IFailure {
    symbol: Symbol;
}

type Success<T> = T
type Failure<T extends IFailure> = T

type Either<T, U extends IFailure> = Success<T> | Failure<U>

function isFailure<T, U extends IFailure>(either: Either<T, U>): either is Failure<U> {
    const candidateFailure = either as Failure<U>;
    return candidateFailure.symbol && candidateFailure.symbol === FailureSymbol;
}

interface Data {
    data: string;
}

interface CustomFailure {
    symbol: Symbol;
    errorMessage: string;
}

let either: Either<Data, CustomFailure> = { data: 'success' }; 

if (isFailure<Data, CustomFailure>(either)) {
    // inside this branch the type of either is shown to be: Data & CustomFailure
    console.log('failure', either);
} else {
    // inside this branch the type of either is show to be: Data
    console.log('success', either)
}

Upvotes: 0

Views: 367

Answers (1)

artem
artem

Reputation: 51659

why the value of either in the isFailure branch is shown as being the intersection type of Data & CustomError as opposed to just CustomError

because isFailure check follows immediately after either was initialized with a value that has Data type. The compiler infers types and uses control flow analysis when appropriate, so in this case it remembers that either value has Data type. You can see it if you try to assign either to something declared as Data, you get no error:

const either2: Data = either; //no error, either has `Data` type here

When either type is not narrowed, it works as expected. Control flow analysis stops at function boundaries, so if you have the same code inside the function it will be inferred as CustsomFailure only:

function test(either: Either<Data, CustomFailure>) {
  if (isFailure<Data, CustomFailure>(either)) {
      // here either is CustomFailure
      console.log('failure', either);
  } else {
      // inside this branch the type of either is show to be: Data
      console.log('success', either)
  }
}

Upvotes: 2

Related Questions