Skip to content

Tuple with conditional optional type #31409

Closed

Description

@Krzyrok

Summary:
I can define in typescript that tuple contains optional elements, e.g. let array: [string?]; - I can assign empty array. But can I combine this with conditional (generic) type: when type is defined then value should be set. Otherwise, value is disallowed. Or at least optional if not disallowed.

TypeScript Version:
3.5.0-dev.20190515

Search Terms:
tuple with conditional optional type

Code

interface A<T = never> {
    array: [
        [T] extends [never] ? never : T, // how to make this `never` as optional?
        // smth like this does not work:
        // [T] extends [void] ? never? : T1, //error: `JSDoc types can only be used inside of documentation comments`
        // T? // optional works here correctly
    ]
}

let test1: A<string>;
test1.array = ["some string"]
test1.array = [] // error: correctly

let test2: A;
test2.array = [ "" ] // error: correctly
test2.array = [ undefined ] // error: correctly
test2.array = [ null ] // error: correctly
test2.array = []; // error: INCORRECTLY! `Property '0' is missing in type '[]' but required in type '[never]'.`

Expected behavior:
I can assign empty array to test2.array. Even more - I cannot assign not empty array.

Actual behavior:
I cannot assign empty array. I cannot assign also array with some value. I have to use some other type and pass it explicitly, e.g. undefined:

interface A<T = never> {
    array: [
        [T] extends [never] ? undefined : T
    ]
}

let test1: A<string>;
test1.array = ["some string"]
test1.array = [] // error: correctly

let test2: A;
test2.array = [ "" ] // error: correctly
test2.array = [ undefined ] // error: correctly
test2.array = [ null ] // no error: is it correct?
test2.array = []; // error: INCORRECTLY! `Property '0' is missing in type '[]' but required in type '[never]'.`

Playground Link

Activity

  1. AlCalzone commented on May 15, 2019

    @AlCalzone
    Contributor
    interface A<T = never> {
        array: [T] extends [never] ? [] : [T]
    }

    works

  2. Krzyrok commented on May 15, 2019

    @Krzyrok
    Author

    OK, it works. There are more generic types and first one can be defined and second one omitted (and more types). But your response apply also to this.

    interface A<T1 = never, T2 = never> {
        array: [T1] extends [never] // if T1 is not defined, then T2 is also not defined by sure
            ? []
            : [T2] extends [never]
                ? [T1]
                : [T1, T2]
    }
    
    let test1: A<string>;
    test1.array = ["some string"]
    test1.array = [] // error: correctly
    
    let test2: A;
    test2.array = [ "" ] // error: correctly
    test2.array = [ undefined ] // error: correctly
    test2.array = [ null ] // error: correctly
    test2.array = [];
    
    let test3: A<string, number>;
    test3.array = ["some string", 55]
    test3.array = [] // error: correctly

    playground

    This below looks better but I'm happy that above one is working

    interface A<T1 = never, T2 = never> {
        array: [
            [T1] extends [never] ? never? : T1,
            [T2] extends [never] ? never? : T2
        ]
    }

    Thanks a lot!

  3. Krzyrok commented on May 16, 2019

    @Krzyrok
    Author

    I've encountered another problem with new solution - I need to call reduce on array to create string (T1 and T2 extends string keys of another class - so it should be possible).

    Simplified code:

    class GenericTupple<T1, T2> {
        tupple: [T1] | [T1, T2];
    
        getString() {
            // This one does not work :/
            // error: `Type 'string' is not assignable to type 'T1'.
            return this.tupple.reduce((final, prev) => `${final}.${prev}`, '');
        }
    }

    playground

    casting helps:

    return (this.tupple as [T1, T2?]).reduce((final, prev) => `${final}.${prev}`, '');

    This results from original code (from comment above) with conditional types (so I cannot define tupple as: tupple: [T1, T2?];:

    class A<T1, T2 = never, T3 = never> {
        array: [T2] extends [never]
            ? [T1]
            : [T3] extends [never]
                ? [T1, T2]
                : [T1, T2, T3]
    
        getString(): string {
            // error: `Type 'string' is not assignable to type 'T1'.`
            return this.array.reduce((final, prev) => `${final}.${prev}`, '');
        }
    }

    playground

    Is it possible without casting? As I can see, incorrect reduce is detected:
    reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
    instead of
    reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;

    Should I create new issue (this is connected a little with original question so for now I opened this one - am I missing something obvious)?

  4. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions