Typescript: Suggestion: a Type Assertion that asserts at run time

Created on 20 Mar 2015  ·  7Comments  ·  Source: microsoft/TypeScript

Type Assertions are compile time only.

It would be helpful to have a concise way to have a compile time type assertion be checked at run time.

The language spec gives the example:

var shape = createShape(shapeKind);
if (shape instanceof Circle) { 
  var circle = <Circle> shape;
  ...
} else {
  throw new WrongTypeAssertionException("Wanted type Circle, but was actually " + (typeof shape));
}

which is very repetitive (and fragile). Instead it would be great to have something much more concise like:

var circle = <Circle!> createShape(shapeKind);

that threw an exception at run time if an instanceof check failed.

I would rather give up some run time performance for a concise way to get a clear run time failure message if my assertion is false.

Out of Scope Suggestion

Most helpful comment

Just in case anyone is interested in the future, here’s the runtime type assertion that also accepts abstract class types (based on the constructor type definition proposed in this Stack Overflow post):

type Constructor<T> = Function & { prototype: T }

function cast<T>(type: Constructor<T>, object: Object): T {
    if (!(object instanceof type)) {
        throw new TypeError();
    }
    return <T>object;
}

All 7 comments

You didn't read about type guards and union types, did you?

The following code is perfectly valid. shape.radius inside the if statement doesn't lead to a compilation error because the shape variable is of the type Circle there.

class Shape { }
class Circle extends Shape {
    constructor(public radius: number) {
        super();
    }
}

function createShape(kind) {
    if (kind === "circle") {
        return new Circle(200);
    }
    return new Shape();
}

var shape = createShape("circle");
if (shape instanceof Circle) { 
  console.log(shape.radius);
} else {
  throw Error("Wanted type Circle");
}

Yes, I have read about type guards and union types.

I understand that your example is valid code.

As I said above, implementing the type check by hand using instanceof is repetitive and fragile.

Instead of writing the type assertion manually like (A):

var shape = createShape("circle");
if (shape instanceof Circle) { 
  console.log(shape.radius);
} else {
  throw Error("Wanted type Circle but got " + (typeof shape));
}

I want to write something like (B):

var circle = <Circle!> createShape("circle");
console.log(circle.radius);

(note the '!' in the Type Assertion) and have it be equivalent to the manual type assertion in (A) above.

Here is how I implemented this behavior, if it can help.

interface Type<T> {
    new (...args: any[]): T;
}

function cast<T>(type: Type<T>, object: Object): T {
    if (!(object instanceof type)) {
        var matches = /^[^ ]+ (\w+)/.exec(type.toString());
        var expected = matches ? matches[1] : "?";
        matches = /^[^ ]+ (\w+)/.exec(object.constructor.toString());
        var got = matches ? matches[1] : "?";
        throw new TypeError(`expected type ${expected}, got ${got}`);
    }
    return <T>object;
}

So can write:

var circle = cast(Circle, createShape("circle"));
console.log(circle.radius);

Concerning your request, I would prefer a compilation option that adds code to validate all the casts that the compiler cannot statically confirm.
I would enable this option in debug mode and remove it in release mode.

@stephanedr Thank you for your example implementation.

I agree that it would be ideal if TypeScript could generate code for this feature only if it can statically prove the run time check is necessary.

This is not something we can do in a library function!

If the performance is good enough (< 20% slower?) I would leave these checks on even in release mode.

It might be safer if the compiler warn on unnecessary casts and always synthesize a check otherwise.

This has been suggested and discussed many times. We're always trying to avoid pushing the type system into the runtime and the number of types this actually works for (pretty much _just_ classes and primitives) isn't large enough to justify the complexity versus the use cases it enables.

Just in case anyone is interested in the future, here’s the runtime type assertion that also accepts abstract class types (based on the constructor type definition proposed in this Stack Overflow post):

type Constructor<T> = Function & { prototype: T }

function cast<T>(type: Constructor<T>, object: Object): T {
    if (!(object instanceof type)) {
        throw new TypeError();
    }
    return <T>object;
}
Was this page helpful?
0 / 5 - 0 ratings