## Background In frameworks like Ember the following pattern would be very common: ```ts import * as Ember from 'ember'; const { computed, get } = Ember; interface myComponent { myProp: Ember.ComputedProperty<number>; someMethodThatWouldUseMyProp(): any; } var myComponent = { myProp: computed.readOnly<number>('someDep'), someMethodThatWouldUseMyProp() { const myPropValue = get<myComponent, 'myProp'>(this, 'myProp'); } }; ``` Having a declaration file for Ember like the following: ```ts declare namespace Ember { class ComputedProperty<T> { get(keyName: string): T; /* some more props omitted */ } namespace computed { interface readOnly { <T>(dependentKey: string): ComputedProperty<T>; } function readOnly<T>(dependentKey: string): ComputedProperty<T>; } interface get { <T, K extends keyof T>(obj: T, keyName: K): T[K]; } function get<T, K extends keyof T>(obj: T, keyName: K): T[K]; } export = Ember; ``` would result in `const myPropValue = get<myComponent, 'myProp'>(this, 'myProp');` inferring a type as `Ember.ComputedProperty<number>` which is not what we really want as the way `get` method works is not just `T[K]` but : `T[K]` and then `if` the inferred type is `Ember.ComputedProperty<F>` it would actually return those `F` like so: ```ts type F = number; interface myComponent { myProp: Ember.ComputedProperty<F>; someMethodThatWouldUseMyProp(): any; } var myComponent = { myProp: computed.readOnly<number>('someDep'), someMethodThatWouldUseMyProp() { const myPropValue: F = get<myComponent, 'myProp'>(this, 'myProp'); } }; ``` ## Proposal The simplest way to achieve proper behaviour at least as it seems would be to allow using nesting with static types for dynamically named properties: ```ts interface get { <T, K extends keyof T, F extends keyof T[K]>(obj: T, keyName: K): T[K][F]; } function get<T, K extends keyof T, F extends keyof T[K]>(obj: T, keyName: K): T[K][F]; ``` and then redefining the `Ember.ComputedProperty` declaration in a such way that we would be able to infer type value from it: ```ts class ComputedProperty<T> { get(keyName: string): T; _value: T; /* some more props omitted */ } ``` we then would be sure that when we have `const myPropValue: F = get<myComponent, 'myProp', '_value'>(this, 'myProp');` we would end up with the correct type for `myPropValue`. Now however this way of defining and using `get` function would throw: `TS2344 Type '_value' does not satisfy the constraint 'never'` Another even better option would be able to have our `get` method declaration like the following: ```ts interface get { <T, K extends keyof T>(obj: T, keyName: K): T[K]["_value"]; } function get<T, K extends keyof T>(obj: T, keyName: K): T[K]["_value"]; ``` This would allow for even more clean code: `const myPropValue: F = get<myComponent, 'myProp'>(this, 'myProp');`. Now it throws either `TS2339 Property '_value' does not exist on type T[K]`. Such approach would also allow for multiple level deep computed properties that are supported in Ember: ```ts interface get { <T, K extends keyof T, F extends keyof T[K]>(obj: T, keyName: K): T[K][F]["_value"]; } function get<T, K extends keyof T, F extends keyof T[K]>(obj: T, keyName: K): T[K][F]["_value"]; const myPropValue = get<myComponent, 'someOtherNestedObj', 'someProp'>(this, 'someOtherNestedObj.myProp'); ``` Ideally if we have #12342 we wouldn't have to define the `_value` in ComputedProperty in the declaration files and use the result of the `get` method instead: ```ts class ComputedProperty<T> { get(keyName: string): T; /* some more props omitted */ } interface get { <T, K extends keyof T, F extends keyof T[K]>(obj: T, keyName: K): ReturnType<T[K][F]["get"]>; } function get<T, K extends keyof T, F extends keyof T[K]>(obj: T, keyName: K): ReturnType<T[K][F]["get"]>; ``` ## Related: #11929 #12342