2

I want to a way pass myG to Foo and get completions(in vscode) for Foo.get and for the OBJ/KEY_OF_OBJ constructor arg, i can get it to work so that i get completions for Foo.get or for OBJ/KEY_OF_OBJ, but not all at the same time.

interface G {
  [key: string]: string;
}
interface GE<T extends G> {
  [key: string]: T;
}

class Foo<T extends G, K extends GE<T>> {
  constructor(OBJ: K, KEY_OF_OBJ: keyof K) {}
  get(keyOfG: keyof T) {}
}

interface myG extends G {
  d: string;
  e: string;
}

const foo = new Foo<myG>(
  {
    a: {
      d: '',
      e: ''
    },
    c: {
      d: '',
      e: ''
    }
  },
  'c'
);
foo.get('');

edit: having to declare Foo like this new Foo<myG, {a: myG, c: myG}> is also acceptable, my only issue with declaring it like this is, that Foo.get doesn't get autocompletion which is because of the ts thinks its a string thing, if i remove G, it works, but i want T to have key: stringValue pairs, i guess my problem is kind of solved, unless there is a way to constrain T to only have string values.

user12415891
  • 67
  • 1
  • 6
  • There are multiple things going on here, each of which could be its own question. First: you seem to want to specify the `T` generic type parameter while having the compiler infer the `K` generic type parameter when you call `new Foo(....)`. But TypeScript doesn't support this sort of partial inference for type parameters, so [you'd need to use a workaround for it](https://stackoverflow.com/a/55754981/2887218). Continued: – jcalz Dec 25 '19 at 02:44
  • The other issue is that you have a value of type `string | "d" | "e"` and you want the compiler to autocomplete `"d"` and `"e"`... but of course `string | "d" | "e"` is just `string` and the compiler forgets all about `"d"` and `"e"` when suggesting completions. This is [an open issue](https://github.com/microsoft/TypeScript/issues/33471) and there are workarounds for that too. Which of the two issues are you asking about in the question? – jcalz Dec 25 '19 at 02:45

2 Answers2

2

I will assume that you're okay with constructing Foo instances by manually specifying both the T and K generic type parameters:

const foo = new Foo<myG, { a: myG, c: myG }>(
    {
        a: { d: "", e: "" },
        c: { d: "", e: "" }
    }, "c"
);

So your question is: how can we get autocompletion in our IDE for the known literal keys "d" and "e" of myG, when the string index signature absorbs "d" and "e" into it? That is, since "d" | "e" | string evaluates to just string, how do we get the IDE to "remember" the "d" and "e"?

There is an open issue (Microsoft/TypeScript#33471) about this, so there's no easy and obvious answer yet. What complex and/or non-obvious answers are there? Here's one.

First, use an existing solution to get the known literal keys from a type with an index signature:

type KnownKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;

Let's verify that it does the right thing with myG:

type KnownKeysOfMyG = KnownKeys<myG>
// type KnownKeysOfMyG = "d" | "e"

Looks good. Now, we can change the get() signature of Foo to accept KnownKeys<T>:

get(keyOfG: Extract<KnownKeys<T>, string>): void;

If we still want to accept arbitrary string values in get(), the workaround here is to add a second overload signature for get() that accepts string:

  get(keyOfG: Extract<KnownKeys<T>, string>): void;
  get(keyOfG: string): void;
  get(keyofG: string) {
     // impl
  }

It still accepts all string values:

foo.get(""); // okay
foo.get("d"); // okay
foo.get("e"); // okay
foo.get("fkjksjfksjfkds"); // okay

But the autocomplete hinting now works:

enter image description here

Okay, hope that helps; good luck!

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
1

I don't think typescript has a way to know that at compile time.

You basically are saying that G can have the properties of any string, so myG extends that and has 2 required properties d and e, but it's still possible to add any new property, with any name, from type string. So at compile time Typescript is unable to know all the assigned properties from type string.

The only way to get a completion is if you define a small set of strings on the G interface.

interface G {
 a ?: string;
 b ?: string;
 c ?: string;
 d ?: string;
 e ?: string;
}

This way the get can only expect arguments from "a" | "b" | "c" | "d" | "e"

dege
  • 2,824
  • 2
  • 25
  • 33
  • thanks for your answer, im not marking it as the answer because i feel like there has to be a way to do this, i just don't know how. – user12415891 Dec 24 '19 at 13:55