Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Multiple level deep Static types for dynamically named properties. #12407

Closed
canufeel opened this issue Nov 21, 2016 · 2 comments
Assignees
Labels
Needs More Info The issue still hasn't been fully clarified

Comments

@canufeel
Copy link

canufeel commented Nov 21, 2016

Background

In frameworks like Ember the following pattern would be very common:

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:

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:

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:

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:

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:

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:

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:

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

@canufeel canufeel changed the title Feature request: Static types for dynamically named properties work only one level deep. Feature request: Multiple level deep Static types for dynamically named properties. Nov 21, 2016
@HerringtonDarkholme
Copy link
Contributor

Thanks for raising this up. Also related #12349

@DanielRosenwasser DanielRosenwasser self-assigned this Apr 24, 2017
@DanielRosenwasser DanielRosenwasser added the Needs More Info The issue still hasn't been fully clarified label Apr 24, 2017
@RyanCavanaugh
Copy link
Member

Based on playing with this, it looks like these use cases are now fully solvable with conditional types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified
Projects
None yet
Development

No branches or pull requests

4 participants