maembe
maembe

Reputation: 1300

Record with Self-referencing Generic Type

I have a function that looks like this:

function create<A extends 
   Record<string, (r:{[k in keyof A]: ReturnType<A[k]>}) => any>>   //the Record Value is a function that takes the keys and return values of A as it's argument
   (options:A) : any {
   ...
}

so A is a Record with values that are functions that I would like to take the record keys with the return types for those keys as the values. Something like:

create({
    test: () => "TEST",
    lower: (r) => r.test.toLowerCase() //r is any here, I want r to have the keys of this record with the values being the Return types of these values
}

Is there a way to do this while getting typings for the other values in the same Record?

Upvotes: 0

Views: 1937

Answers (2)

Daniil Loban
Daniil Loban

Reputation: 4381

I'm not sure that I catch idea:

type OptionsObject<Map> = {
  [K in keyof Map]: ((K?: any) => Map[K]);
}

function create<Map>(options: any): OptionsObject<Map> {
  return options;
}

const opt = {
  test:  () => "TeSt",
  lower: (r: any) => r.test(r).toLowerCase(),
  upper: (r: any) => r.test(r).toUpperCase()
}

const a = create<typeof opt>(opt)

console.log('a.test()',  a.test())    // "a.test()",  "TeSt" 
console.log('a.lower(a)', a.lower(a)) // "a.lower(a)",  "test" 
console.log('a.upper(a)', a.upper(a)) // "a.upper(a)",  "TEST" 

Upvotes: 1

Linda Paiste
Linda Paiste

Reputation: 42188

If you define your options as Record<string, ...>, that means that every string is a valid key of options. So you would not be able to distinguish between valid and invalid keys.

One solution is to use the generic to describe the keys of our mapper. This is not perfect -- we know that r.test exists and is a function, but we don't know the return type and we don't know that it doesn't actually use the argument so we have to pass r to call it.

type OptionsObject<Keys extends PropertyKey> = {
  [K in Keys]: (r: OptionsObject<Keys>) => any;
}

function create<Keys extends PropertyKey>
   (options: OptionsObject<Keys>) : any {
}

create({
    test: () => "TEST",
    lower: (r) => r.test(r).toLowerCase()
})

We can get a bit better by using the generic to describe a Map object of the keys and return types. This is able to recognize that test returns string, but it can't figure out lower and it infers the return type as unknown.

type OptionsObject<Map> = {
  [K in keyof Map]: (r: OptionsObject<Map>) => Map[K];
}

function create<Map extends {}>
   (options: OptionsObject<Map>) : any {
}

create({
    test: () => "TEST",
    lower: (r) => r.test(r).toLowerCase()
})

edit: I think I misunderstood this bit "the Record Value is a function that takes the keys and return values of A as it's argument" and what you want is (r: Map) => instead of (r: OptionsObject<Map>) =>.

Upvotes: 1

Related Questions