#Cannot inference ternary condition

12 messages · Page 1 of 1 (latest)

upbeat peak
#

Why is it saying string is not assignable to [string | number, boolean][] when we know that it will only ever return string when concatToString is true?

scenic spire
#

Yeah so typeof concatToString is going to be effectively the same as just writing boolean. In other words you'll always get a union. To prove this to yourself write:

declare const getStringValuesFunction: GetStringValuesFunction;
const result = getStringValuesFunction({ concatToString: false, includeFunction: true, includeCommas: true });

result; // This will be typed as `string | Array<[string | number, boolean]>` despite the explicit `concatToString: false`.
#

To solve it from the type perspective you need to make it generic:

type GetStringValuesFunction = <const ConcatToString extends boolean>(options: { includeFunction: boolean; includeCommas: boolean; concatToString: ConcatToString }) => ConcatToString extends true ? string : Array<[string | number, boolean]>;
#

To solve it exactly the way you've asked from the runtime perspective you probably have to explicitly cast the return type, TypeScript doesn't seem to infer conditional types as a return value:

type ConcatReturn<ConcatToString extends boolean> = ConcatToString extends true ? string : Array<[string | number, boolean]>;

function test<ConcatToString extends boolean>({ concatToString }: { includeFunction: boolean; includeCommas: boolean; concatToString: ConcatToString; }): ConcatReturn<ConcatToString> {
    if (concatToString) {
        return "foo" as ConcatReturn<ConcatToString>;
    } else {
        return [["hello", true]] as ConcatReturn<ConcatToString>;
    }
}
#

To solve it the way I'd recommend would be overloads:

type GetValuesFunctionOptions = {
  includeFunction: boolean;
  includeCommas: boolean;
  concatToString: boolean;
}

function test(options: GetValuesFunctionOptions & { concatToString: true }): string
function test(options: GetValuesFunctionOptions & { concatToString: false }): Array<[string | number, boolean]>

// Implementation
function test(options: GetValuesFunctionOptions): string | Array<[string | number, boolean]> {
    if (options.concatToString) {
        return "hello"
    } else {
        return [["hello", true]];
    }
}
#

please note, this isn't any more sound. You could just have easily have written if (options.concatToString) { return [["hello", true]] } and gotten no warning.

#

It basically does the casts more implicitly but the signature is at least more honest as well.

#

In truth I'd recommend two functions; getValues and getValuesConcat or concatValues(getValues(...)) or something

upbeat peak
scenic spire
#

yup! I decided to give you several options to show the pros and cons of each

#

I didn't want to pass judgement myself because maybe you're typing a .d.ts file

#

and in that context you can't control the function bodies anyways so you'd have to take one of the approaches I showed