DollarAkshay
DollarAkshay

Reputation: 2122

Typescript generic constraint not working when it is a variable property

I am trying to get typescript generic to work when it is being used to access an object's property dynamically (inside square brackets).

**Minimal Example : **

export type TaskType = 'withdraw' | 'pickup'

interface Task<T extends TaskType = TaskType> {
  val: number
}

interface Memory {
  tasks : {
    [T in TaskType] : {
      [targetId: string]: Task<T>
    }
  }
}

const data: Memory = {
  tasks : {
    withdraw : {
      'a' : {val:  0}
    },
    pickup : {
      'a' : {val: 0}
    }
  }
}

const res1 =  data.tasks.withdraw.a; // Type is set correctly to Task<"withdraw">


let fn = function<T extends TaskType>(taskType: T) {
  const res2 = data.tasks[taskType].a; 
  // ^ Type here is not being infered. It is Task<"withdraw"> | Task<"pickup">
};

Link to TS Playground

As you can see in the above example when called outside the function with a specific key res1 returns the correct type.

But when using a variable data.tasks[taskType].a; it dosent work. Type of res2 should be Task<T>, instead it is something else. What am I doing wrong ?

UPDATE : More Info

I feel like I should expand a bit more on what my actual problem is. The actual Task interface does not overlap between different TaskTypes. Also I have a function like the one shown below.

interface Task<T extends TaskType = TaskType> {
  val: number
  resourceType: T extends 'withdraw' ? string : undefined
}
...
let claim = <T extends TaskType>(taskType: T, task: Task<T>) => {
  data.tasks[taskType].a = task;  // Why is task not assignable here ?
}

Link to TS Playground

Since Task<T> between various string do no have the same properties, the line data.tasks[taskType].a = task; throws an error.

I am calling the claim function like so :

const pickupTask = {val: 0, resourceType: undefined} as Task<`pickup`>;
claim('pickup', pickupTask) // The function will always be called with the same tasktype i.e. calling claim('withdraw', pickupTask) should throw an error

Logically speaking the parameter pickupTask should be able to be assigned to data.tasks[taskType].a since taskType='pickup'. So why is it throwing an error ? I might have misunderstood typescript generics, but I am not sure where I am wrong.

Also I cannot change Memory interface to be a generic interface.

Upvotes: 0

Views: 367

Answers (1)

You need to make small refactor of types and make data one of the arguments.

export type TaskType = 'withdraw' | 'pickup'

interface Task<T extends TaskType = TaskType> {
  val: number
}

type Memory<Type extends TaskType> = {
  tasks: {
    [T in Type]: {
      [targetId: string]: Task<T>
    }
  }
}

const data: Memory<TaskType> = {
  tasks: {
    withdraw: {
      'a': { val: 0 }
    },
    pickup: {
      'a': { val: 0 }
    }
  }
}

const res1 = data.tasks.withdraw.a; // Type is set correctly to Task<"withdraw">


const fn = <
  Key extends TaskType,
  Data extends Memory<Key>
>(data: Data, taskType: Key) => {
  const res2 = data.tasks[taskType].a; // Task<Key>
};

Memory - should expect feneric Type

Playground

Upvotes: 1

Related Questions