Search code examples
typescriptenumsintersection

Type 'string' is not assignable to type 'never'.ts(2322)


I have an object of two Domains plus three Events per Domain. Now I want to pick one Event and its resp. Domain. I get the following error.

Type 'string' is not assignable to type 'never'.ts(2322)
Untitled-1(8, 5): The expected type comes from property 'Event' which is declared here on type
'Group<{ Domain0: Enum<"Event0_0" | "Event0_1" | "Event0_2">; Domain1: Enum<"Event1_0" | "Event1_1" | "Event1_2">; },
"Domain0" | "Domain1">'

If I have only one Domain, everything is working fine, so I assume, TS takes some kind of intersection, thus type never.

type Enum<K extends PropertyKey> = {
    [key in K]: key;
};

type Events = Record<string, Enum<string>>;

interface Group<events extends Events, domain extends keyof events> {
    Domain: domain;
    Event: keyof events[domain];
};

interface Obj<events extends Events> {
    Group: Group<events, keyof events>;
};

const obj: Obj<{
    Domain0: Enum<"Event0_0" | "Event0_1" | "Event0_2">,
    Domain1: Enum<"Event1_0" | "Event1_1" | "Event1_2">,
}> = {
    Group: { Domain: "Domain0", Event: "Event0_0" },
};

Enum<"a" | "b"> simply is an object of shape

{
    a = "a",
    b = "b",
}

Solution

  • Let's go through this step-by-step.

    You are passing the following object into Obj as events.

    {
        Domain0: Enum<"Event0_0" | "Event0_1" | "Event0_2">,
        Domain1: Enum<"Event1_0" | "Event1_1" | "Event1_2">,
    }
    

    This object type and its keys are passed to Group. The type of domain is keyof events which evaluates to:

    "Domain0" | "Domain1"
    

    For the Event property, you compute keyof events[domain]. Fully expanded, this would look like this:

    keyof {
        Domain0: Enum<"Event0_0" | "Event0_1" | "Event0_2">,
        Domain1: Enum<"Event1_0" | "Event1_1" | "Event1_2">,
    }["Domain0" | "Domain1"]
    

    events[domain] would evaluate to

    Enum<"Event0_0" | "Event0_1" | "Event0_2"> 
      | Enum<"Event1_0" | "Event1_1" | "Event1_2">
    

    which is a union of two object types which do not share any properties. Calling keyof on this union therefore produces never.

    keyof (
      | {
          Event0_0: "Event0_0";
          Event0_1: "Event0_1";
          Event0_2: "Event0_2";
        }
      | {
          Event1_0: "Event1_0";
          Event1_1: "Event1_1";
          Event1_2: "Event1_2";
        }
    )
    // -> never
    

    To achieve your initial goal, you have to compute a union of valid Domain/Event combinations.

    type Group<E extends Events> = {
        [K in keyof E]: {
            Domain: K;
            Event: keyof E[K];
        }
    }[keyof E]
    
    interface Obj<E extends Events> {
        Group: Group<E>;
    }
    

    Which leads to the following result:

    let obj: Obj<{
        Domain0: Enum<"Event0_0" | "Event0_1" | "Event0_2">,
        Domain1: Enum<"Event1_0" | "Event1_1" | "Event1_2">,
    }>
    
    obj = {
        Group: { Domain: "Domain0", Event: "Event0_0" },
    };
    obj = {
        Group: { Domain: "Domain0", Event: "Event1_0" },
    //  ^^^^^ Type '"Domain0"' is not assignable to type '"Domain1"
    };
    

    Playground