Home > Blockchain >  Why doesn't a TypeScript "Branded Type" differentiate in this case?
Why doesn't a TypeScript "Branded Type" differentiate in this case?

Time:01-05

Conditional tests comparing indexed types using different branded strings doesn't seem to differentiate as I expect.

type Brand<K, T> = K & { __type: T };

type CatID = Brand<string,'cat'>;
type BirdID = Brand<string,'bird'>;

const x1: {[id:CatID]:string} extends {[id:BirdID]:boolean} ? true : false;
//    ^? const x1: true

const x2: {[id in CatID]:string} extends {[id in BirdID]:boolean} ? true : false;
//    ^? const x2: true

const x3: Record<CatID,string> extends Record<BirdID,boolean> ? true : false;
//    ^? const x3: true

const x4: CatID extends BirdID ? true : false;
//    ^? const x4: false

Playground Link

The indexes are different types (branded differently, at least), and even the values are different types (string vs. boolean). Why would those first three extends conditionals return true? As a final test, I checked if the branded types "extend" each other, and typescript reports that they do not.

Any insight here appreciated. I'd like to be able to differentiate types by their index signature.

CodePudding user response:

I can't speak to the deep "why" you're looking for, but re:

I'd like to be able to differentiate types by their index signature

...you can tell those types apart using keyof TheObjectType extends CatID:

type Brand<K, T> = K & { __type: T };

type CatID = Brand<string, "cat">;
type BirdID = Brand<string, "bird">;

type Cats = {
    [id: CatID]: string;
};

type Birds = {
    [id: BirdID]: string;
};

type CatCheckAgainstCatID = keyof Cats extends CatID ? true : false;
//   ^? type CatCheckAgainstCatID = true

type CatCheckAgainstBirdId = keyof Cats extends BirdID ? true : false;
//   ^? type CatCheckAgainstBirdId = false

type BirdCheckAgainstCatID = keyof Birds extends CatID ? true : false;
//   ^? type BirdCheckAgainstCatID = false

type BirdCheckAgainstBirdID = keyof Birds extends BirdID ? true : false;
//   ^? type BirdCheckAgainstBirdID = true

Playground link

Beware, though, that a union can confuse the issue (as is often the case):

type Chimera = Birds | Cats;

type X = keyof Chimera extends CatID ? true : false;
//   ^? type X = true
type Y = keyof Chimera extends BirdID ? true : false;
//   ^? type Y = true

Playground link

  • Related