#Can TS infer types in my function body if the types are conditional?

31 messages · Page 1 of 1 (latest)

willow nest
#

!ts

#

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?

jovial krakenBOT
#
bawdyinkslinger#0

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
...```

willow nest
#

!ts

jovial krakenBOT
#
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;
}
willow nest
#

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

scenic vector
#

typeof noDelta is just boolean

#

So true extends typeof noDelta will always evaluates to true.

willow nest
#

This compiles, but the return type isn't as specific as I'd like. Is this the best I can do?

#

!ts

jovial krakenBOT
#
bawdyinkslinger#0

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
...```

willow nest
#

!ts

jovial krakenBOT
#
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;
}
scenic vector
#

You can either:

  • Make noDelta a 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.

scenic vector
#

Split it up to two functions stateMarshal/stateMarshalWithDelta.

scenic vector
#

But even then it's debatable.

willow nest
#

What about conditional parameters?

willow nest
scenic vector
#

Not sure what you mean by conditional parameters, but should be fine.

willow nest
scenic vector
#

Yeah those are fine.

willow nest
#

Why is that?

scenic vector
#

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