Skip to content

Distributive conditional type limit over 25 items? #46497

Closed

Description

Bug Report

I'm not exactly sure what the bug is because it feels rather arbitrary, but I suspect there's some limit to distributive conditional types I'm running into. The repro below (tried to make it as minimal as possible) is the easiest way to demonstrate it.

The problem is I have a conditional type Conditional<T> where I pass in C = A | B and B itself is a union type composed of 26 items. In the conditional type, I branch on T extends A ? AWrapper<A> : T extends B ? BWrapper\<B\> : .....

I then make Conditional the return type of a function, and try to return a simplified case of BWrapper<B>, but it fails to compile. The interesting part though is it only fails when B is a union type of 26 items and not when it's just 25 items.

Everything also works if I change the conditional type to be non-distributive: [T] extends [A] ? AWrapper<A> : [T] extends [B] ? BWrapper<B> : ...

I'm sure a suggestion might be to make Conditional a union type of AWrapper<A> | BWrapper<B> here instead of a conditional type, but I explain why at the bottom I'd prefer to avoid that.

In summary, I'm not sure why when B is > 25 items it doesn't work.

🔎 Search Terms

25 26 enum distributive conditional type limit

🕗 Version & Regression Information

I see this bug in all versions of TS that are usable in the TS playground

⏯ Playground Link

Playground link with relevant code

💻 Code

enum FieldWithoutId {
    NOID1,
    NOID2,
    NOID3,
    NOID4,
    NOID5,
    NOID6,
    NOID7,
    NOID8,
    NOID9,
    NOID10,
    NOID11,
    NOID12,
    NOID13,
    NOID14,
    NOID15,
    NOID16,
    NOID17,
    NOID18,
    NOID19,
    NOID20,
    NOID21,
    NOID22,
    NOID23,
    NOID24,
    NOID25,
    NOID26, // commenting out this line makes everything work
}

enum FieldWithId {
    ID1,
    ID2
}

type WrapperWithId<T extends FieldWithId = FieldWithId> = {
    field: T;
    id: number;
};

type WrapperWithoutId<
    T extends FieldWithoutId = FieldWithoutId
> = {
    field: T;
};

type Field = FieldWithId | FieldWithoutId;

type Wrapper<T extends Field = Field> = T extends FieldWithoutId ? WrapperWithoutId<T>
: T extends FieldWithId ? WrapperWithId<T> : WrapperWithId | WrapperWithoutId;

const toWrapper = (field: Field, id?: number): Wrapper => {
    if (field === FieldWithId.ID1 || field === FieldWithId.ID2) {
        if (!id) {
            throw new Error();
        }
        return {
            field,
            id,
        };
    } else {
        const x: FieldWithoutId = field;
        // fails to compile. Is distribution of conditional type too large?
        return { field: x };
    }
}

// Using a "non-distributive" conditional type. Not sure if that's the correct description or what makes this non-distributive though. Wording taken from 
type NonDistributiveWrapper<T extends Field = Field> = [T] extends [FieldWithoutId] ? WrapperWithoutId<T>
: [T] extends [FieldWithId] ? WrapperWithId<T> : WrapperWithId | WrapperWithoutId;

const toNonDistributiveWrapper = (field: Field, id?: number): NonDistributiveWrapper => {
    if (field === FieldWithId.ID1 || field === FieldWithId.ID2) {
        if (!id) {
            throw new Error();
        }
        return {
            field,
            id,
        };
    } else {
        const x: FieldWithoutId = field;
        return { field: x }; // works. Why?
    }
}

// Want to be able to do this, which prevents making Wrapper a union type?
type Container<T extends Field = Field> = {
    wrapper: Wrapper<T>;
    value: T extends FieldWithoutId ? undefined : number;
}

function foo(container: Container<FieldWithId>) {
    return container.wrapper.id + container.value;
}

// If we try to make Wrapper a union type instead (which is reasonable), we can't do that:
export type Wrapper2 = WrapperWithId | WrapperWithoutId;

type ContainerNoParameterizedWrapper<T extends Field = Field> = {
    wrapper: Wrapper2;
    value: T extends FieldWithoutId ? undefined : number;
}

function foo2(container: ContainerNoParameterizedWrapper<FieldWithId>) {
    return container.wrapper.id + container.value; // fails to compile
}

🙁 Actual behavior

Error:

Type '{ field: FieldWithoutId; }' is not assignable to type 'WrapperWithId<FieldWithId.ID1> | WrapperWithId<FieldWithId.ID2> | WrapperWithoutId<FieldWithoutId.NOID1> | ... 24 more ... | WrapperWithoutId<...>'.
  Type '{ field: FieldWithoutId; }' is not assignable to type 'WrapperWithoutId<FieldWithoutId.NOID26>'.
    Types of property 'field' are incompatible.
      Type 'FieldWithoutId' is not assignable to type 'FieldWithoutId.NOID26'

🙂 Expected behavior

No error, which is what happens in the non-distributive case or the case where FieldWithoutId only contains 25 items.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions