#generic that extends boolean with default value

34 messages · Page 1 of 1 (latest)

strong hull
#

if multiple is false, the return type is T, and if multiple is true, the return type is T[]

export async function askListQuestion<T extends string, U extends boolean>(
    question: string,
    options: T[],
    multiple: U,
): Promise<U extends true ? T[] : T> {
    return (
        await inquirer.prompt<{ answer: U extends true ? T[] : T }>({
            name: 'answer',
            message: question,
            type: multiple ? 'checkbox' : 'list',
            choices: options as string[],
        })
    ).answer
}

but, i cant do multiple: U = false, for some reason, i get this error

Type 'boolean' is not assignable to type 'U'.
  'boolean' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'boolean'.

and if i do multiple?: U, then it works if i explicitly pass true or false, but if i dont send anything, it says its T | T[], when i expected it to be T (because undefined doesnt extends true, right?)
so, any idea how to make it treat an undefined as a false?

wooden sand
#

conditionals don't work in signatures

#

use overloads or split this into 2 functions

strong hull
glass plank
#

I consider "conditional return type depending on argument type" to generally be an anti pattern.

#

You either use an overload, or better yet split the function into two.

#

The signature you wrote is also wrong, if U is boolean then your function returns Promise<T[]> which is wrong, it really should be returning Promise<T[] | T>. Nevermind this, it does work because generic distributes.

strong hull
#

but its T[] | T

const x = await askListQuestion('choose', ['a', 'b'], false as boolean)
    //    ^? const x: "a" | "b" | ("a" | "b")[]
#

wym by "generic distributes"?

#

and is that a noun or a verb

glass plank
#

!hb distributive

hardy bobcatBOT
glass plank
#

Basically your when U is a union (boolean is just a union of true | false), your code U extends true ? T[] : T gets distributed:

// from
(true | false) extends true ? T[] : T
// to
(true extends true ? T[] : T) | (false extends true ? T[] : T)

Which correctly results in T[] | T.

#

But I've seen a lot of incorrectly written conditional by other people and jumped the gun.

strong hull
#

i see, but then, why did you think it would be T[]? if it wasnt for the distributive thing, should it be T?

#

like, boolean doesnt extends true, so, the alternate part should apply, the T, right?

glass plank
#

Eh it would be T, my bad.

strong hull
#

i mean alternate

#

there is a lot of weird stuff in ts that i dont know, i should read the docs more

glass plank
#

As for your original question, it's because someone can do askListQuestion<..., true>(..., ...) but your multiple: U = false will conflict with the type.

strong hull
#

ok, so, as i understand it, extends X means that it can be X or a subtype of X

#

so, is there a way to say it has to be X?

glass plank
#

Yes, by not using the generic.

strong hull
#

but without the generic, i cant do the conditional return, right?

glass plank
#

fn<T extends X>(arg: T) but if you say "T must be X" then that means that generic is just useless and you just want fn(arg: X).

strong hull
#

yea, bc what i really want is to change the return type depending on the param

glass plank
#

The problem is not extends, it's that caller can always specify the generic type parameter.

strong hull
#

ok, so what i want is basically not possible? and i have to use signature overloading?

#

or whatever its called

glass plank
#

No it's not possible, and yes you have to either user function overload, or just split into two functions.

#

"Conditional return type depending on argument type" is pretty useless for the most part.

strong hull
#

well, obviously it makes sense to separate different behaviors into separated functions

#

but i just wanna know the limits

glass plank
#

I don't see a difference between "pickOneOrMultiple(...)/pickOneOrMultiple(..., true)" vs "pickOne(...)/pickMultiple(...)."