KingOfCoders
KingOfCoders

Reputation: 2282

Typescript code compiles although type should be flagged

I have this code to model a result type. The Failure type would contain all possible failures. 'Incorrect' is not one of them, but I nevertheless can return Incorrect as a Failure.

type Try<T, E> = Success<T> | Failures<E> | E;

// User code

type Failure = FileNotFound | NameNotFound;

class FileNotFound extends BaseFailure {}
class NameNotFound extends BaseFailure {}

class Incorrect extends BaseFailure {}

type Result<T> = Try<T, Failure>

function y() {
  let x1 = x(1);
  if (x1.isSuccess) {
  } else {
    x1.hasMany;
  }
}

function x(a:number): Result<String> {
  if (a>3) {
    return new Success("Hallo");
  } else {
    return new Incorrect();
  }
}

Upvotes: 1

Views: 34

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249706

Typescript uses structural compatibility to determine type compatibility. So when it decides whether the class Incorrect is compatible with Failure is compares the structure of Incorrect with the members of the Failure union. Doing this it will find that FileNotFound has the same structure as Incorrect and are thus compatible.

To get around this you can add a member (preferably private) to all classes in the union. This for example will fail:

class Success<T> { constructor(public value: T) { } isSuccess: true = true }
type Try<T, E> = Success<T> | E;

class BaseFailure {
    isSuccess: false
}

type Failure = FileNotFound | NameNotFound;

class FileNotFound extends BaseFailure { private _type: "filenotfound" }
class NameNotFound extends BaseFailure { private _type: "namenotfound" }

class Incorrect extends BaseFailure { }

type Result<T> = Try<T, Failure>

function y() {
    let x1 = x(1);
    if (x1.isSuccess) {
    } else {
    }
}

function x(a: number): Result<String> {
    if (a > 3) {
        return new Success("Hallo");
    } else {
        return new Incorrect(); // error now
    }
}

You can also include the private field in BaseFailure to force implementers to specify it:

class BaseFailure<T> {
    isSuccess: false
    private __type: T
}


class FileNotFound extends BaseFailure<"filenotfound"> { }
class NameNotFound extends BaseFailure<"namenotfound"> { }

class Incorrect extends BaseFailure<"incorrect"> { }

Allowing some nominal typing has long been discussed (see issue) but for now this is the best we can do

Upvotes: 1

Related Questions