#Understanding conditional types

20 messages · Page 1 of 1 (latest)

sudden kindle
#

Hi, I am a bit confused with how conditional types play with return types of functions. e.g. I would have expected the following bit of code to work but it fails with Type '{ key: T; }' is not assignable to type 'Ret<T>' playground link

type Ret<T> = T extends string ? { key: string } : { key: number };

function fun<T extends string>(x: T): Ret<T> {
  return {
    key: x,
  };
}

My thinking is that since T is assignable to a string, Ret<T> should expand to be { key: string }. Even if I change Ret<T> to return a { key: T } it still fails. Thanks in advance!

woeful sparrowBOT
#

@sudden kindle Here's a shortened URL of your playground link! You can remove the full link from your message.

.ateas#0

Preview:```ts
type Ret<T> = T extends string
? {key: string}
: {key: number}

function fun<T extends string>(x: T): Ret<T> {
return {
key: x,
}
}```

rapid scaffold
#

how conditional types play with return types of functions
they kinda don't

sudden kindle
#

Ah damn, is there any docs explaining this behaviour? curious to learn more 🙏 .
On that note, how would I represent a return type that is dependent on the generic passed in?

rapid scaffold
#

is there any docs explaining this behaviour
no, not really. they're just 2 features that don't interact well
conditionals in signatures in general, i mean

#

generally you would either find a way to remove the conditional, or split the signature into overloads

sudden kindle
#

thanks boss ill have a go with making a overload

knotty flower
# rapid scaffold > how conditional types play with return types of functions they kinda don't

they will though. it was going to land in TS 5.8 but got deferred, hopefully 5.9. see https://github.com/microsoft/TypeScript/pull/56941

GitHub

Fixes #33912.
Fixes #33014.
Motivation
Sometimes we want to write functions whose return type is picked between different options, depending on the type of a parameter. For instance:
declare const ...

rapid scaffold
#

cc @sudden kindle see the above

knotty flower
#

though i don't think that will do anything for @sudden kindle's original code. the function implementation isn't conditional at all in fun

#

you could return Ret<string> to make that work. i might need to see some real code though to understand why you'd want to write the type signature this way

rapid scaffold
#

oh wait the proposal you linked is about handling conditional flow to fulfill the generic return type, right?

knotty flower
#

yeah

#

check this out:

rapid scaffold
#

yeah i see

woeful sparrowBOT
#
mkantor#0

Preview:```ts
type Ret<T> = T extends string
? {key: string}
: {key: number}

function fun<T extends string>(x: T): Ret<T> {
return {
key: x,
}
}

function exampleThatWillWork<T extends boolean>(
x: T
): T extends true
? string
: T extends false
? number
: never
...```

knotty flower
#

that's using the 5.8 beta before that change was yanked

sudden kindle
#

Ah ok thanks, what I am actually trying to do is type a function that diffs two objects (e.g. A and B). For each key in the obj A and B, there will be a value in the output object (with keys from A | B) stating whether the key is new, already exists or is deleted. I cant seem to do this without conditional types (since the output type depends on if the object key values are primitives, lists or another object)

#

something like this:

type Primitive = string | boolean | number | undefined;

type PrimitiveDelta<T extends Primitive> = {
  type: 'UNCHANGED'
  value: T
} | {
  type: 'REPLACE'
  value: Primitive | DiffableObject
}

type DiffableObject = { [key: string]: Primitive | DiffableObject }

type ObjectDelta<T extends DiffableObject> = {[K in keyof T]:{
  type: 'UNCHANGED'
  value: DiffResult<T[K]>
} | {
  type: 'INSERT',
  key: string,
  value: any,
} | {
  type: 'DELETE',
  key: string
}}

type DiffResult<T extends Primitive | DiffableObject> = T extends Primitive ? PrimitiveDelta<T> 
: T extends DiffableObject ?  ObjectDelta<T>
 : undefined;