#ReturnType of a dependent generic

57 messages · Page 1 of 1 (latest)

coarse musk
#

How come ReturnType<Generic> is only accurate when the target generic doesn't receive args?

const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends A extends undefined ? (...args: any[]) => any : (args: ReturnType<A>) => any,
>(args: { a?: A, b?: B }) => {}

MakeGeneric({
  a: () => 'string',
  b: (args) => '', // CORRECT: args is "string"
})

MakeGeneric({
  a: (args) => 'string',
  b: (args) => '', // INCORRECT: args is "any"
})

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAsgQwNYFMDiKwoE4EtgwC8MAPAFAwwCCMKAHlJgCYQwAUAdFwtgOYQAuGAjABPANoBdAJREAfMLEAaGBRgAhWg2asa9RmBYwAroZQAzXFiYwA-Oy4ce-ISIkz5i0TCFtngmAAlFChjbDAAFVEABxQSKjlZQgU3JTI5Pz4AgG9hWyEqFQAjfI0YAF8khWzysjJEVAwsPGA2bLUEXyqYAHJoPDBeHrTKSgB6MZgAPVs1ccmabBCwyGEYfqteAG4YJeAUXAA3FFYwEGEstSLff26e4bnR+enZp6eJmABhAHlAwIBRT4RVxZGC4VgbQYqCBWfbUXbLcIoGyQ3hkSp1BroTA4fBtDo3LJ3VEPd6TGaPD6LRGrBDrKADbYI-ZHE4XfhXQn8O6kt7PCl854AdUC3wAcmgQfx1gALEDGAA2NiKKHpjOhsNV1NCSJRDM26OkQA

worldly auroraBOT
#

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

rem#7642

Preview:```ts
const MakeGeneric = <
A extends (...args: any[]) => any,
B extends A extends undefined
? (...args: any[]) => any
: (args: ReturnType<A>) => any

(args: {
a?: A
b?: B
}) => {}

MakeGeneric({
a: () => "string",
// ^?
// A returns a string; re
...```

clear lynx
#

why A extends undefined?

#

also, why not ReturnType<A>

coarse musk
#

I want it to be possible to not pass in a function for a, and then b would have the signature (...args: any[]) => any instead of (arg: ReturnType<A>) => anye

worldly auroraBOT
#
const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends A extends undefined ? (...args: any[]) => any : (args: ReturnType<A>) => any,
>(args: { a?: A, b?: B }) => {}

MakeGeneric({
// ^? - const MakeGeneric: <(...args: any[]) => any, (args: any) => string>(args: {
//     a?: ((...args: any[]) => any) | undefined;
//     b?: ((args: any) => string) | undefined;
// }) => void
  b: (args) => '',
})
limpid gorge
#

(by default, a generic is inferred as its constraint

coarse musk
#

Oh ok, thanks

limpid gorge
#

but yeah indeed, changing to B extends (args: ReturnType<A>) => any, works fine

coarse musk
#

I tried something like this

const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends (args: ReturnType<A>) => any,
  C extends (args: ReturnType<B>) => any,
>(args: { a?: A, b?: B, c?: C }) => {}

MakeGeneric({
  a: (args) => 'string',
          // ^?
    // A returns a string; receives args
  b: (args) => '',
         // ^?
  c: (args) => 123,
         // ^?
})
limpid gorge
#

!ts

worldly auroraBOT
#

:x:

Invalid QuickInfo query

The request on line 10 in /usr/src/app/index.ts for quickinfo via ^? returned no from the compiler.

This is likely that the x positioning is off.

limpid gorge
#

:|

coarse musk
#

and it types the args of C as any, but the args of B as string, so I'm not sure what to do

#
const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends (args: ReturnType<A>) => any,
  C extends (args: ReturnType<B>) => any,
>(args: { a?: A, b?: B, c?: C }) => {}

MakeGeneric({
  a: (args) => 'string',
         // ^?
    // A returns a string; receives args
  b: (args) => '',
         // ^?
  c: (args) => 123,
         // ^?
})
#

!ts

worldly auroraBOT
#
const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends (args: ReturnType<A>) => any,
  C extends (args: ReturnType<B>) => any,
>(args: { a?: A, b?: B, c?: C }) => {}

MakeGeneric({
  a: (args) => 'string',
//          ^? - function(args: any): string
    // A returns a string; receives args
  b: (args) => '',
//          ^? - function(args: string): string
  c: (args) => 123,
//          ^? - function(args: any): number
})
elder siren
#

Because TypeScript computed the B generic when MakeGeneric = ...

#
const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends (A extends undefined ? '123' : (args: ReturnType<A>) => any),
>(args: { a?: A, b?: B }) => {}
#
const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends (args: any) => any,
>(args: { a?: A, b?: B }) => {}
#

The above two code snippets have the same effect

#

B extends (A extends undefined ? '123' : (args: ReturnType<A>) => any) become (args: any) => any when the function is created

coarse musk
#
const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends (args: ReturnType<A>) => any,
  C extends (args: ReturnType<B>) => any,
>(args: { a?: A, b?: B, c?: C }) => {}

MakeGeneric({
  a: (args) => '',
        // ^?
  b: (args) => 123,
        // ^?
  c: (args) => false
        // ^?
})

!ts

#

Is it possible to ask TypeScript to "wait" to compute B and C generics?

elder siren
#

extends is the same as an operator

coarse musk
#

I could use the builder pattern with chaining to narrow the types with every method call, but I was curious to know if there was any way to do it in a single step

elder siren
#

Sorry, idk

#

Or you can use chain class

coarse musk
#

I appreciate your help! 🙏

limpid gorge
#

well

#

avoiding using an object is a possible workaround

worldly auroraBOT
limpid gorge
#

!ts

worldly auroraBOT
#
// 8<
const MakeGeneric = <
  A extends (...args: any[]) => any, 
  B extends (args: ReturnType<A>) => any,
  C extends (args: ReturnType<B>) => any,
>(a: A | undefined, b: B | undefined, c: C | undefined) => {}

MakeGeneric(
  (args) => '',
//       ^? - function(args: any): string
  (args) => 123,
//       ^? - function(args: string): number
  (args) => false
//       ^? - function(args: number): boolean
)```
coarse musk
#

I didn't expect that, thanks! Why does that work differently thonk

elder siren
#

Because in the function generics, the first extends is narrow when using the function that generates the generic type

limpid gorge
#

ah, of course

elder siren
#

But other extends just as the operator, when the function created generates the type

limpid gorge
#

well

#

no that's not why

#

they are all constraints

#

if they are at the top level of a generic parameter, it's a constraint

#

the issue is that inference is not very smart

#

and so you have to infer the object all at once

elder siren
#

yep

limpid gorge
#

since B depends on A, it must be inferred after A

limpid gorge
elder siren
#

or typescript type gymnastics can do this

#

but I think using the chain class is a better choice

coarse musk
#

I'll just do that so I don't have to deal with the TypeScript gymnastics lol

#

Thanks for all the help , I was really confused with this

elder siren
#

no worries

#

!closed

worldly auroraBOT
#

:warning: Only the asker can change the status of a help post

coarse musk
#

!closed