Renan Cidale
Renan Cidale

Reputation: 912

Typescript infer information from object in one of its keys

Having

const a = {
  propOne: {
    methodOne(arg1, arg2: number) {
      console.info('hello...')
    }
  },
  proptwo: {
    b(fn, arg2) {
      fn('methodOne', 'hello') // Error
      fn('methodOne', 1) // OK
    }
  }
}

I'm trying to make this implementation work where I get the type Error and Intelisense when trying to use fn, in my b method. So basically fn first argument is one of the keys of propOne, and fn second argument is based on which method you selected. Since in the example I selected fn with 'methodOne' the second argument is then inferred to be number since methodOne's arg2 is of type number.

I was able to make this work if I write the types for propOne methods, but I wanted to know if there is a better way to approach this.

Upvotes: 1

Views: 66

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250366

You can get this to mostly work, except for what I think is a bug that we will get to later.

The first problem is we will need to use a function in order to help with out inference. Simple variables can have their type inferred from initialization expression or specified in a type annotation, for more complex behavior we need to use the inference behavior of functions.

We will use two type parameters, one for propOne and one for propTwo (which I have renamed as actions and mutations as we discussed on twitter, the original names were just stand-ins for Vue store props)

Once we have the type parameters captured, it is just a matter of transforming the type parameter for actions into a suitable parameter for the function

type Func = (...a: any[]) => void;
type SkipFirstParameter<T extends Func> = T extends (a: any, ... r: infer P) => any ? P : [];
type Actions<M extends Record<string, Func>> = <MK extends keyof M>(mutation: MK, ...a: SkipFirstParameter<M[MK]>) => void

function create<T, M extends Record<string, Func>, A extends Record<string, (f: Actions<M>, ...a: any[]) => void>>(o: {    
  mutations: M
  actions: A
}) {
  return o
}
const a = create({
  mutations: {
    methodOne: (arg1: number, arg2: number) => {
      console.info('hello...')
    },
  },
  actions: {
    b: (fn, arg2: number)  => {
      fn("methodOne", arg2)
      fn("methodOne", "arg2")
    }
  }
});

Playground Link

You will notice I switched to arrow functions, instead of shorthand syntax or function expression. This is because inference behaves differently between arrow functions and the others. I believe this to be a compiler bug and field one here but if we use method of function syntax M will be inferred as unknown. This probably has something to do with the way TS tries to infer this for such functions, but the difference in behavior is probably a bug.

Upvotes: 1

Related Questions