How are TypeScript generics different from Java?

435 Views Asked by At

The following code would produce the expected type error in Java, but TypeScript doesn't consider it an error. Is this by design, or is it a bug in TypeScript?

abstract class UnitType<T extends UnitType<T>> {
    ...
}

class Unit<T extends UnitType<T>> {
    ...
}

class Length extends UnitType<Length> {
    static meters: Unit<Length> = new Unit<Length>()
}
class Angle extends UnitType<Angle> {
    static degrees: Unit<Angle> = new Unit<Angle>()
}


class UnitizedNumber<T extends UnitType<T>> {
    constructor(value: number, unit: Unit<T>) {
        ...
    }
}

// Length and Angle are not compatible, so this should be an error.
const foo: UnitizedNumber<Length> = new UnitizedNumber<Length>(1, Angle.degrees)

In fact TypeScript doesn't even consider this an error:

const meters: Length = new Angle()

Does TypeScript treat all classes as structural instead of nominal types? I'm used to Java and Flow treating classes as nominal types.

1

There are 1 best solutions below

0
On

Okay, my suspicion was correct, TypeScript currently treats classes as structural types but more support for nominal types is planned.

According to https://michalzalecki.com/nominal-typing-in-typescript/ one way to force TypeScript to treat a class like a nominal type is to use a private property:

class Length extends UnitType<Length> {
    private __nominal: void
    static meters: Unit<Length> = new Unit<Length>()
}
class Angle extends UnitType<Angle> {
    private __nominal: void
    static degrees: Unit<Angle> = new Unit<Angle>()
}

However this still doesn't cause the desired errors above. I had to make sure that the type parameter for Unit is actually used to cause an error:

class Unit<T extends UnitType<T>> {
    type: T
    constructor(type: T) {
        this.type = type
    }
}

const foo: UnitizedNumber<Length> = new UnitizedNumber<Length>(1, Angle.degrees)

Argument of type 'Unit' is not assignable to parameter of type 'Unit'. Type 'Angle' is not assignable to type 'Length'. Types have separate declarations of a private property 'name'. (2345)