1
class Target {
  prop1 = "321";
}

const target = new Target()

class Target2 {
  prop2 = "123";
}

const target2 = new Target2()

type TValue<V extends object> = {
  target: V;
  key: keyof V;
}

class Case<V extends TValue<any>[]> {
  constructor(private values: V) {}
}

const someCase = new Case([
  {
    target: target,
    key: "123"
  },
  {
    target: target2,
    key: "prop2"
  }
])

The target type is successfully inferred, but the keyof is not working as expected. What am I doing wrong?

I also created a playground.

Just Alex
  • 183
  • 1
  • 12
  • What are you trying to do here? The closest I can think of case 1 is `const value1: TValue = { target: new Target(), key: "prop1" }` - you need to supply a *type*, you also need to use an *instance* for `target`. – VLAZ Jul 22 '21 at 10:11
  • I have updated the example. It was just a mistake. – Just Alex Jul 22 '21 at 10:12
  • In case 2, the target type is inferred automatically, but in case 1 it is not. Why? – Just Alex Jul 22 '21 at 10:15
  • Bacause in case 1 there is nothing to infer it *from*. You're directly saying that you want `TValue` with no generic attribute. Which uses the default for the generic type: `{}`. So, declaring `TValue` is the same as doing `TValue<{}>` – VLAZ Jul 22 '21 at 10:17
  • Why can't this be inferred from *value*? – Just Alex Jul 22 '21 at 10:21
  • Again, you're *directly* declaring `TValue` without passing a generic. Which is the same as declaring `TValue<{}>` because that's the default if no generic is passed in. Generics are usually not inferred on assignment. It makes no real sense to do it, considering TS only has structural typing. `T` can be anything and many different things can be assignable to `T`. Inferring the correct one will require adding mind reading functionality to TS. – VLAZ Jul 22 '21 at 10:24
  • 1
    @JustAlex let me know if this answer https://stackoverflow.com/questions/68470384/typescript-array-of-a-generic-with-different-args#answer-68475483 helps you – captain-yossarian from Ukraine Jul 22 '21 at 10:53

1 Answers1

1

My previous answer is similar to your use case, but it differs a bit because I have used a function and you are dealing with class.


type Entity<Obj, Key> = {
  target: Obj,
  key: Key
}

type IsValid<T extends Entity<any, any>[]> =
  /**
   * Infer each element of the array
   */
  T[number] extends infer Elem
  /**
   * Check if every element of the array extends Entity
   */
  ? Elem extends Entity<any, any>
  /**
   * Check if keyof Elem['object'] extends `key` property
   * 1) if [key] property is one of object properties - return true
   * 2) if at least one element does not meet your requirements return false | true,
   * because some element are ok
   */
  ? keyof Elem['target'] extends Elem['key']
  ? true
  : false
  : false
  : false;

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

// credits https://stackoverflow.com/users/125734/titian-cernicova-dragomir
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

type Validator<T extends boolean> =
  /**
   * If IsValid returns false | true (boolean) it means Error
   * otherwise - ok
   */
  IsUnion<T> extends true ?
  ['Dear developer, please do smth right']
  /**
   * I'm using empty array here, because 
   * (...flag:[])=>any evaluates to function without arguments
   */
  : []

class Case<
  Value extends { [prop: string]: any },
  Key extends keyof Value,
  Data extends Entity<Value, Key>[],
  > {
  constructor(private values: [...Data], ...flag: [...Validator<IsValid<[...Data]>>]) { }
}


class Target {
  prop1 = "321";
}

const target = new Target()

class Target2 {
  prop2 = "123";
}

const target2 = new Target2()

const result = new Case([{
  target: target, key: 'prop1'
},
{
  target: target2, key: 'prop2'
}]) // ok

const result_ = new Case([{
  target: target, key: 'prop1'
},
{
  target: target2, key: 'prop1' // wrong
}]) // error

You can replace ['Dear developer, please do smth right'] with [never]

Here you can find more information about infering function arguments