Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: a Type Assertion that asserts at run time #2444

Closed
neilgalarneau opened this issue Mar 20, 2015 · 7 comments
Closed

Suggestion: a Type Assertion that asserts at run time #2444

neilgalarneau opened this issue Mar 20, 2015 · 7 comments
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript

Comments

@neilgalarneau
Copy link

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.

@thorn0
Copy link

thorn0 commented Mar 20, 2015

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");
}

@neilgalarneau
Copy link
Author

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.

@stephanedr
Copy link

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.

@neilgalarneau
Copy link
Author

@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.

@qc00
Copy link

qc00 commented Mar 23, 2015

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

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Out of Scope This idea sits outside of the TypeScript language design constraints labels Mar 23, 2015
@RyanCavanaugh
Copy link
Member

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.

@yvt
Copy link

yvt commented Nov 13, 2017

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;
}

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants