TypeScript also supports some really advanced typing I've yet to see in other languages like template literal types (don't @ me, I mostly work with dynamic languages at my current job). I feel like even the TS docs haven't caught up to the full power of TS's full featureset
1. Use an enum
2. Use an intersection with an enum and literally anything else (except a number)
3. Use a private field (only works with classes)
4. Use a `declare const FOO: unique symbol` keyed object intersected with anything else (works with everything including numbers, lots of boilerplate to declare a new nominal type).
In particular iirc you can't use your nominal/branded type as a map key in a type-safe way.
enum A {}
type B = { a: number } & A;
function newB(i:number) {
return { a: i } as B;
}
let b:B = newB(5);
let m:Map<B, string> = new Map();
m.set(b, "test");
m.set({ a: 2 }, "test"); // errors declare const NOMINAL_BRAND: unique symbol;
enum UserIdType {}
type UserId = string & {[NOMINAL_BRAND]: UserIdType}
type UserMap<V> = { [key: UserId]: V };
But at some point in the past few years, this is now supported and things like this work as expected: const userId: UserId = "234" as UserId
const coolUsers: UserMap<boolean> = {}
coolUsers[userId] = true // works
coolUsers["isThisOkay"] = false // failsBut anyway, what I want from TS is
type UserId = unique string; // or something
const userId = "1234" as UserId
and no further boilerplate or trickery to get it to work. Would certainly be nice.But that won't be added because there is no native JavaScript equivalent.
So to differentiate behavior depending of which part of sum type you got you need some value in the "real world" like a tag that can enable you to do that. Because all the type information is gone at runtime.
Strongest violation of this rule I know of are enums that generate their own JS code, but they are their separate thing. Something like sum types would need to be present everywhere.
Honestly I kind of like it this way. I'm trying out Rust right now and I'm very often surprised how what I do in one spot depends on huge context of all the generic types that can create wildly different behavior (mostly huge variety compile type error messages in surprisingly varied and distant spots) depending on what types they infer.
The other issue with the typesystem is the (soon to be obviated) need to compile to decent javascript and interact with it. That made sense before WASM existed and it will make a lot less sense once the component model is stabilized.