#optional argument with inference

39 messages · Page 1 of 1 (latest)

opaque stoneBOT
#
xibread#0

Preview:```ts
type AType = { a: { value: boolean } }
type BType = { b: { value: number } }

type Unwrap<T> = { -readonly [K in keyof T]: T[K][keyof T[K]] } & {}

declare function testFoo<const T extends AType | AType & BType>(arg: T): Unwrap<T>

// want { a: true }
const one = testFoo({ a: { value: true } })
...```

cloud dragon
#

(note: the & {} is just so the type information simplifies, it's not needed)

nocturne spruce
#

could you explain why that helper type is necessary?

#

can it be achieved without adding the Unwrap<T> in the function signature or is that a losing battle?

opaque stoneBOT
#
strobe#9977

Preview:```ts
type AType = { a: { value: boolean } }
type BType = { b: { value: number } }

type Unwrap<T> = { -readonly [K in keyof T]: T[K][keyof T[K]] } & {}

function testFoo<const T extends AType | AType & BType>(arg: T) {
const result: Unwrap<T> = (
'b' in arg
...```

nocturne spruce
#

this doesn't work, but it looks close?

dark aurora
#

I don't think there's a way to implement it without casting.

#

Depends on what you are actually doing, might consider just splitting it into two function.

nocturne spruce
#

i'm trying to keep it under one function

#

how would it look with casting?

dark aurora
#

Don't give result a type, and just cast the return with as never or something.

nocturne spruce
#
function testFoo<const T extends AType | AType & BType>(arg: T) {
    const result = (
        'b' in arg 
            ? { a: arg.a.value, b: arg.b.value } 
            : { a: arg.a.value }
    )
    return result as never
}

like that?

dark aurora
#

Yeah.

#

You basically have no type safety though.

nocturne spruce
#

if i cast as never the type is never though, so it's not satisfying the wants

dark aurora
#

Oh you don't have a return type on your function

nocturne spruce
#

yeah i'm seeing if it's possible without that currently

dark aurora
#

Either annotate Unwrap<T> to your function return type

#

Or cast with as Unwrap<T>.

nocturne spruce
#

return result as Unwrap<T> works.
how come you can't do it with satisfies or concrete type?

dark aurora
#

Because what you have is essential a conditional return type

#

T has to be narrowed in order to resolve what Unwrap<T> is, but T cannot be narrowed because it's a generic type parameter.

nocturne spruce
#

what do you mean by T must be narrowed to resolve exactly

dark aurora
#

T has to be narrowed to either A or A & B.

nocturne spruce
#

ok and it can't be narrowed why?

dark aurora
#

Generic type parameters just can't be narrowed.

nocturne spruce
#

so if it wasn't a generic, it would narrow properly in the function?

dark aurora
#

Yep, if you change your function signature to non generic:

function testFoo(arg: AType | AType & BType): Unwrap<AType | AType & BType>

You can implement without casting.

#

Well... that's not really a good example.

dark aurora
#

But yeah, the point is generic type parameters just can't be narrowed.

#

Yes it is narrowed, hover over arg inside each branch of the ternary and you can see arg is being narrowed to either A & B or A.

#

But keep in mind that's arg being narrowed, not T being narrowed.

#

So Unwrap<T> is still the same as before.

nocturne spruce
nocturne spruce
#

if you didn't use generics, would you still need to cast to something?

#

ignoring the inference of values, assume type is ok

dark aurora