Generic Type Erasure

Topics: General, Language Specification
Jul 1, 2014 at 7:28 PM
Edited Jul 2, 2014 at 7:22 PM
In TypeScript when compiled, the whole type system is erased, however in the case of generics, having type erasure remove all generic type declarations in the compiled js code could be a little problematic!
Obviously you cannot have something like this in TypeScript:
function SomeMethod<T>(n: T) {
    if (n instanceof T) {
        // code implementation
    }
}
.Net preserves generic type information in the compiled code by design so that full runtime introspection of generics is preserved and it's a good thing! However, I don't understand why designers of TypeScript decided to remove all generic type information at runtime?

If I understand it correctly, generics in TypeScript could be applied on classes, methods and interfaces. Given that types could be passed by reference in JavaScript, in each case generic type information could be preserved either by the use of type as an argument or explicit type definition.

The following TypeScript code:
interface ISomeInterface<T> {
}

class SomeClass<TClass> implements ISomeInterface<TClass>{
    SomeMethod<TMethod>(value: TMethod): void {
    }
}

var obj = new SomeClass<number>();
obj.SomeMethod("hello");
could be compiled to this one (using type as argument):
var SomeClass = function () {
    function SomeClass(TClass) {
    }
    SomeClass.prototype.SomeMethod = function (value, TMethod) {
    };
    return SomeClass;
} ();

var obj = new SomeClass(Number);
obj.SomeMethod("hello", String);
or this one (using explicit type definition)
var SomeClass$number = function () {
    var TClass = Number;
    function SomeClass() {
    }
    SomeClass.prototype.SomeMethod = function (value) {
        var TMethod = value == null ? String : value.constructor;
    };
    return SomeClass;
} ();

var obj = new SomeClass$number();
obj.SomeMethod("hello");
Any ideas are appreciated...
Coordinator
Jul 2, 2014 at 5:37 PM
To get at the crux of what I think you're asking about, you want to be able to either pass types as first-class values or reflect from inside the function what type of generic it's being instantiated with? As I'm sure you've noticed, neither is supported in TypeScript.

Perhaps a couple words as to why and then a couple ways you might be able to do something similar:

Types aren't first-class values for functions because there isn't a runtime representation of a type in TypeScript. In truth, that's also true of JavaScript, which TypeScript builds on. For example, in JavaScript if you reflect on a function you find out it's a Function, but short of parsing the function code there aren't ways to get at exactly what the function type is.

Generic types can't be reflected for their shape in part because of the previous note about reflection but also because generics in TypeScript are modular. Meaning from inside of a generic function the only thing you know about your generic type T is what the constraint says is available and nothing else. This keeps you from piercing the veil, so that the API you document is the one you require.

One way to work with modular, erased generics would be to use generic constraints to slip a little bit of reflection in there yourself. Something like:
enum ReflectionClass { AnimalClass, CatClass };

interface Reflection {
  reflectionId: ReflectionClass;
}

function SomeMethod<T extends Reflection>(n: T) {
    if (n.reflectionId == ReflectionClass.AnimalClass) {
        // code implementation for Animals
    }
}
Jul 2, 2014 at 7:11 PM
Edited Jul 2, 2014 at 7:16 PM
I understand that there isn't a runtime representation of custom types in TypeScript or JavaScript, yet I believe there could be generic type information of some sort inside a compiled JavaScript instead of having type erasure wiping out all generic type info!

Turns out any instance of a function (as class) reflects it's type through the __proto__ property! I mean it's correct that you cannot get the type representation of any class in JavaScript using typeof(class) but instanceof operator always knows the true class type of an object by traversing its proto chain!

Besides, I was only making a point by mentioning the lack of instanceof operator for generic types in TypeScript! Turns out there are other issues caused by generic type erasure:
  • All instances of generic classes have the exact same static members. Consider the following:
    If I have a generic class that has static data, then I expect each generic type to have its own copy of that static data. However, TypeScript keeps a single copy of the static data for all generic types.
class SomeClass<T>{
    private static count: number;
}
  • There could not be any new() operator for generic types as there is in C#:
class ItemFactory<T>{
    public getNewItem(): T {
        return new T();
    }
}
  • Casting to or from a generic type is impossible:
class SomeClass {
    OfType<T>(): T {
        return <T>this;
    }
}
Jul 2, 2014 at 8:20 PM
Even if we don't get generic types as parameters, it would be nice if the following would possible:
class SomeClass<T> {
    constructor(type: typeof T) {
        // use type T
        var instance = new T();
    }
}

var myClass: SomeClass<OtherClass> = new SomeClass<OtherClass>(typeof OtherClass);
This would require that the type T was non-primitive, of course.
Jul 2, 2014 at 10:50 PM
There is something close to that already, though not as ideal as you posted:
class SomeClass<T extends {new (...args): any}> {
    constructor(type: T) {
        // use type T
        var instance = new type();
    }
}
class OtherClass { }
var myClass = new SomeClass<typeof OtherClass>(OtherClass);
Jul 3, 2014 at 4:33 AM
That's not actually too awful, it seems.