It is important to note that the key()
method signature within your Accessor
interface should return an indexed access type structured as Config[X]
, where X
represents the type of the parameter k
. Your current approach, using Config[k]
, is incorrect because k
refers to the value of the parameter, not its type. To correct this, you can utilize the typeof
type operator in the following manner:
interface Accessor {
key(k: keyof Config): Config[typeof k];
}
After making this adjustment, the code compiles without any errors and functions properly. For instance:
declare const a: Accessor;
const langProp = a.key("lang");
// langProp: number | "en" | "de" 🤔
const testProp = a.key("test");
// testProp: number | "en" | "de" 🤔
However, this implementation does not achieve the intended behavior. The resulting type from the key()
call signature encompasses all possible property types of Config
, regardless of the input. This occurs because the type of k
is simply keyof Config
, equating to the union "lang" | "test"
. Consequently, Config[typeof k]
becomes Config[keyof Config]
, which resolves to Config["lang"] | Config["test"]
, leading to "en" | "de" | number
. Therefore, the call signature fails to differentiate between different keys passed into it.
To address this issue, you must modify key()
to make it generic with respect to the type of k
. Instead of restricting k
to be of type keyof Config
, it should accept a generic type K
, constrained to keyof Config
:
interface Accessor {
key<K extends keyof Config>(k: K): Config[typeof k];
}
In this updated version, K
serves as a generic type parameter, declared within angle brackets after the method name but before the opening parenthesis for the function parameters. With this change, references to K
are permissible elsewhere in the call signature.
The revised implementation yields the desired outcome:
declare const a: Accessor;
const langProp = a.key("lang");
// langProp: "en" | "de" 👍
const testProp = a.key("test");
// testProp: number 👍
It's worth noting that when a type already has a designated name, the typeof
type operator is redundant. In our scenario, since k
is of type K
, typeof k
can be simplified to just K
:
interface Accessor {
key<K extends keyof Config>(k: K): Config[K];
}
This streamlined version remains functional and achieves the expected results.
Access TypeScript Playground here