Skip to content

Tuple with conditional optional type #31409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Krzyrok opened this issue May 15, 2019 · 3 comments
Closed

Tuple with conditional optional type #31409

Krzyrok opened this issue May 15, 2019 · 3 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@Krzyrok
Copy link

Krzyrok commented May 15, 2019

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

@AlCalzone
Copy link
Contributor

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

works

@Krzyrok
Copy link
Author

Krzyrok commented May 15, 2019

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!

@Krzyrok Krzyrok closed this as completed May 15, 2019
@Krzyrok
Copy link
Author

Krzyrok commented May 16, 2019

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)?

@Krzyrok Krzyrok reopened this May 16, 2019
@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label May 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants