Skip to content

Suggestion: Type Property type #1295

Closed
Closed

Description

Motivations

A lot of JavaScript library/framework/pattern involve computation based on the property name of an object. For example Backbone model, functional transformation pluck, ImmutableJS are all based on such mechanism.

//backbone
var Contact = Backbone.Model.extend({})
var contact = new Contact();
contact.get('name');
contact.set('age', 21);

// ImmutableJS
var map = Immutable.Map({ name: 'François', age: 20 });
map = map.set('age', 21);
map.get('age'); // 21

//pluck
var arr = [{ name: 'François' }, { name: 'Fabien' }];
_.pluck(arr, 'name') // ['François', 'Fabien'];

We can easily understand in those examples the relation between the api and the underlying type constraint.
In the case of the backbone model, it is just a kind of proxy for an object of type :

interface Contact {
  name: string;
  age: number;
}

For the case of pluck, it's a transformation

T[] => U[]

where U is the type of a property of T prop.

However we have no way to express such relation in TypeScript, and ends up with dynamic type.

Proposed solution

The proposed solution is to introduce a new syntax for type T[prop] where prop is an argument of the function using such type as return value or type parameter.
With this new type syntax we could write the following definition :

declare module Backbone {

  class Model<T> {
    get(prop: string): T[prop];
    set(prop: string, value: T[prop]): void;
  }
}

declare module ImmutableJS {
  class Map<T> {
    get(prop: string): T[prop];
    set(prop: string, value: T[prop]): Map<T>;
  }
}

declare function pluck<T>(arr: T[], prop: string): Array<T[prop]>  // or T[prop][] 

This way, when we use our Backbone model, TypeScript could correctly type-check the get and set call.

interface Contact {
  name: string;
  age: number;
}
var contact: Backbone.Model<Contact>;

var age = contact.get('age');
contact.set('name', 3) /// error

The prop constant

Constraint

Obviously the constant must be of a type that can be used as index type (string, number, Symbol).

Case of indexable

Let's give a look at our Map definition:

declare module ImmutableJS {
  class Map<T> {
    get(prop: string): T[string];
    set(prop: string, value: T[string]): Map<T>;
  }
}

If T is indexable, our map inherit of this behavior:

var map = new ImmutableJS.Map<{ [index: string]: number}>;

Now get has for type get(prop: string): number.

Interrogation

Now There is some cases where I have pain to think of a correct behavior, let's start again with our Map definition.
If instead of passing { [index: string]: number } as type parameter we would have given
{ [index: number]: number } should the compiler raise an error ?

if we use pluck with a dynamic expression for prop instead of a constant :

var contactArray: Contact[] = []
function pluckContactArray(prop: string) {
  return _.pluck(myArray, prop);
}

or with a constant that is not a property of the type passed as parameter.
should the call to pluck raise an error since the compiler cannot infer the type T[prop], shoud T[prop] be resolved to {} or any, if so should the compiler with --noImplicitAny raise an error ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

FixedA PR has been merged for this issueHelp WantedYou can do thisSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions