#Can TS infer types in my function body if the types are conditional?
31 messages · Page 1 of 1 (latest)
Basically I want to know if my code errors because I'm doing something wrong or if it's because the language can't do this?
Preview:```ts
declare const _expired: any[]
declare const _history: any[]
declare function historyDeltaEncode(
historyArr: any
): any[]
declare const _prng: any
declare const _activeIndex: number
declare function clone<T>(original: T): T
function stateMarshal(
noD
...```
!ts
declare const _expired: any[];
declare const _history: any[];
declare function historyDeltaEncode(historyArr: any): any[];
declare const _prng: any;
declare const _activeIndex: number;
declare function clone<T>(original: T): T;
function stateMarshal(
noDelta: boolean,
): (true extends typeof noDelta
? { history: typeof _history }
: { delta: ReturnType<typeof historyDeltaEncode> }) & {
index: number;
expired?: typeof _expired;
seed: typeof _prng.seed;
} {
/*
Gather the properties.
*/
const stateObj = {
index: _activeIndex,
} as ReturnType<typeof stateMarshal>;
if (noDelta) {
stateObj.history = clone(_history);
} else {
stateObj.delta = historyDeltaEncode(_history);
// ^^^^^
// Property 'delta' does not exist on type '{ history: any[]; } & { index: number; expired?: any[] | undefined; seed: any; }'.
}
if (_expired.length > 0) {
stateObj.expired = [..._expired];
}
if (_prng !== null) {
stateObj.seed = _prng.seed;
}
return stateObj;
}
I think there is a flaw in the thinking this does what I expect: true extends typeof noDelta
I'm comparing a type to a value
typeof noDelta is just boolean
So true extends typeof noDelta will always evaluates to true.
yeap, that's what I realized
This compiles, but the return type isn't as specific as I'd like. Is this the best I can do?
!ts
Preview:```ts
declare const _expired: any[]
declare const _history: any[]
declare function historyDeltaEncode(
historyArr: any
): any[]
declare const _prng: any
declare const _activeIndex: number
declare function clone<T>(original: T): T
function stateMarshal(noDe
...```
!ts
declare const _expired: any[];
declare const _history: any[];
declare function historyDeltaEncode(historyArr: any): any[];
declare const _prng: any;
declare const _activeIndex: number;
declare function clone<T>(original: T): T;
function stateMarshal(
noDelta: boolean,
): {
history?: typeof _history;
delta?: ReturnType<typeof historyDeltaEncode>;
index: number;
expired?: typeof _expired;
seed: typeof _prng.seed;
} {
/*
Gather the properties.
*/
const stateObj = {
index: _activeIndex,
} as ReturnType<typeof stateMarshal>;
if (noDelta) {
stateObj.history = clone(_history);
} else {
stateObj.delta = historyDeltaEncode(_history);
}
if (_expired.length > 0) {
stateObj.expired = [..._expired];
}
if (_prng !== null) {
stateObj.seed = _prng.seed;
}
return stateObj;
}
You can either:
- Make
noDeltaa generic and then conditional return type on it. - Use function overload.
Either way will not be type safe.
My personal recommendation when it comes to conditional return type, is to just don't use them.
like, in general?
Split it up to two functions stateMarshal/stateMarshalWithDelta.
Imo yes. The one big exception is when you are writing libraries and want better DX for your user.
But even then it's debatable.
What about conditional parameters?
they certainly are a source of headaches for me
Not sure what you mean by conditional parameters, but should be fine.
I meant conditional types in parameters
Yeah those are fine.
Why is that?
The problem is more so "conditional return types are not fine"
Let's imagine a function that generates a random value, it will return either a string or a number depending on the argument type.
declare function generateRandom(type: 'number'): number
declare function generateRandom(type: 'string'): string
declare function generateRandom(type: 'number' | 'string'): number | string
Now let's imagine you have a variable type which is 'number' | 'string'
declare const type: 'number' | 'string'
How would you go about using it to generate a random value, and then use the value afterwards?
const result = generateRandom(type)
// result is number | string
// it can't be used until we narrow it down
if (typeof result === 'number') {
useNumber(result)
} else {
useString(result)
}
Alternatively you can do:
if (type === 'number') {
const result = generateRandom(type)
useNumber(result)
} else {
const result = generateRandom(type)
useString(result)
}
The critical insight here is that: result cannot be used unless you already know what type is.
However, if you already know what type is, you might as well just have specialized functions:
if (type === 'number') {
const result = generateRandomNumber()
useNumber(result)
} else {
const result = generateRandomString()
useString(result)
}