{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1295","repository_url":"https://api.github.com/repos/Microsoft/TypeScript","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1295/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1295/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1295/events","html_url":"https://github.com/Microsoft/TypeScript/issues/1295","id":50384290,"number":1295,"title":"Suggestion: Type Property type","user":{"login":"fdecampredon","id":864201,"avatar_url":"https://avatars.githubusercontent.com/u/864201?v=3","gravatar_id":"","url":"https://api.github.com/users/fdecampredon","html_url":"https://github.com/fdecampredon","followers_url":"https://api.github.com/users/fdecampredon/followers","following_url":"https://api.github.com/users/fdecampredon/following{/other_user}","gists_url":"https://api.github.com/users/fdecampredon/gists{/gist_id}","starred_url":"https://api.github.com/users/fdecampredon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fdecampredon/subscriptions","organizations_url":"https://api.github.com/users/fdecampredon/orgs","repos_url":"https://api.github.com/users/fdecampredon/repos","events_url":"https://api.github.com/users/fdecampredon/events{/privacy}","received_events_url":"https://api.github.com/users/fdecampredon/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Accepting%20PRs","name":"Accepting PRs","color":"009800"},{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Suggestion","name":"Suggestion","color":"006b75"}],"state":"open","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/Microsoft/TypeScript/milestones/2","html_url":"https://github.com/Microsoft/TypeScript/milestones/Community","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/milestones/2/labels","id":721044,"number":2,"title":"Community","description":"Issues in the 'Community' milestone are ones which we are accepting pull requests for","creator":{"login":"RyanCavanaugh","id":6685088,"avatar_url":"https://avatars.githubusercontent.com/u/6685088?v=3","gravatar_id":"","url":"https://api.github.com/users/RyanCavanaugh","html_url":"https://github.com/RyanCavanaugh","followers_url":"https://api.github.com/users/RyanCavanaugh/followers","following_url":"https://api.github.com/users/RyanCavanaugh/following{/other_user}","gists_url":"https://api.github.com/users/RyanCavanaugh/gists{/gist_id}","starred_url":"https://api.github.com/users/RyanCavanaugh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RyanCavanaugh/subscriptions","organizations_url":"https://api.github.com/users/RyanCavanaugh/orgs","repos_url":"https://api.github.com/users/RyanCavanaugh/repos","events_url":"https://api.github.com/users/RyanCavanaugh/events{/privacy}","received_events_url":"https://api.github.com/users/RyanCavanaugh/received_events","type":"User","site_admin":false},"open_issues":168,"closed_issues":81,"state":"open","created_at":"2014-07-14T23:24:58Z","updated_at":"2016-03-21T20:22:20Z","due_on":null,"closed_at":null},"comments":38,"created_at":"2014-11-28T15:31:28Z","updated_at":"2016-03-22T20:23:08Z","closed_at":null,"body":"## Motivations\r\n\r\nA lot of JavaScript library/framework/pattern involve computation based on the property name of an object. For example [Backbone](http://backbonejs.org/) model, functional transformation `pluck`, [ImmutableJS](https://github.com/facebook/immutable-js) are all based on such mechanism. \r\n\r\n```JavaScript\r\n//backbone\r\nvar Contact = Backbone.Model.extend({})\r\nvar contact = new Contact();\r\ncontact.get('name');\r\ncontact.set('age', 21);\r\n\r\n// ImmutableJS\r\nvar map = Immutable.Map({ name: 'François', age: 20 });\r\nmap = map.set('age', 21);\r\nmap.get('age'); // 21\r\n\r\n//pluck\r\nvar arr = [{ name: 'François' }, { name: 'Fabien' }];\r\n_.pluck(arr, 'name') // ['François', 'Fabien'];\r\n```\r\nWe can easily understand in those examples the relation between the api and the underlying type constraint.\r\nIn the case of the backbone model, it is just a kind of *proxy* for an object of type : \r\n```typescript\r\ninterface Contact {\r\n  name: string;\r\n  age: number;\r\n}\r\n```\r\nFor the case of `pluck`, it's a transformation \r\n```\r\nT[] => U[]\r\n```\r\nwhere U is the type of a property of T `prop`.\r\n\r\nHowever we have no way to express such relation in TypeScript, and ends up with dynamic type.\r\n\r\n## Proposed solution\r\nThe 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.\r\nWith this new type syntax we could write the following definition : \r\n\r\n```typescript\r\ndeclare module Backbone {\r\n\r\n  class Model<T> {\r\n    get(prop: string): T[prop];\r\n    set(prop: string, value: T[prop]): void;\r\n  }\r\n}\r\n\r\ndeclare module ImmutableJS {\r\n  class Map<T> {\r\n    get(prop: string): T[prop];\r\n    set(prop: string, value: T[prop]): Map<T>;\r\n  }\r\n}\r\n\r\ndeclare function pluck<T>(arr: T[], prop: string): Array<T[prop]>  // or T[prop][] \r\n```\r\n\r\nThis way, when we use our Backbone model, TypeScript could correctly type-check the `get` and `set` call.\r\n\r\n```typescript\r\ninterface Contact {\r\n  name: string;\r\n  age: number;\r\n}\r\nvar contact: Backbone.Model<Contact>;\r\n\r\nvar age = contact.get('age');\r\ncontact.set('name', 3) /// error\r\n```\r\n\r\n## The `prop` constant\r\n\r\n### Constraint\r\nObviously the constant must be of a type that can be used as  index type (`string`, `number`, `Symbol`).\r\n\r\n### Case of indexable\r\n\r\nLet's give a look at our `Map` definition: \r\n\r\n```typescript\r\ndeclare module ImmutableJS {\r\n  class Map<T> {\r\n    get(prop: string): T[string];\r\n    set(prop: string, value: T[string]): Map<T>;\r\n  }\r\n}\r\n```\r\n\r\nIf `T` is indexable, our map inherit of this behavior: \r\n```typescript\r\nvar map = new ImmutableJS.Map<{ [index: string]: number}>;\r\n```\r\nNow `get` has for type `get(prop: string): number`.\r\n\r\n## Interrogation \r\n\r\nNow There is some cases where I have pain to think of a *correct* behavior, let's start again with our `Map` definition.\r\nIf instead of passing `{ [index: string]: number }` as type parameter we would have given \r\n`{ [index: number]: number }` should the compiler raise an error ? \r\n\r\nif we use `pluck` with a dynamic expression for prop instead of a constant : \r\n```typescript\r\nvar contactArray: Contact[] = []\r\nfunction pluckContactArray(prop: string) {\r\n  return _.pluck(myArray, prop);\r\n}\r\n```\r\nor with a constant that is not a property of the type passed as parameter.\r\nshould 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 ?\r\n\r\n\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/200004749","html_url":"https://github.com/Microsoft/TypeScript/issues/1295#issuecomment-200004749","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1295","id":200004749,"user":{"login":"jods4","id":3832820,"avatar_url":"https://avatars.githubusercontent.com/u/3832820?v=3","gravatar_id":"","url":"https://api.github.com/users/jods4","html_url":"https://github.com/jods4","followers_url":"https://api.github.com/users/jods4/followers","following_url":"https://api.github.com/users/jods4/following{/other_user}","gists_url":"https://api.github.com/users/jods4/gists{/gist_id}","starred_url":"https://api.github.com/users/jods4/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jods4/subscriptions","organizations_url":"https://api.github.com/users/jods4/orgs","repos_url":"https://api.github.com/users/jods4/repos","events_url":"https://api.github.com/users/jods4/events{/privacy}","received_events_url":"https://api.github.com/users/jods4/received_events","type":"User","site_admin":false},"created_at":"2016-03-22T20:23:08Z","updated_at":"2016-03-22T20:23:08Z","body":"Approaches above seem:\r\n- quite complicated;\r\n- don't tackle the usage of strings in code. `string` are not \"Find all references\"-able, nor refactorable (e.g. renames).\r\n- some only support \"1st level\" properties and not more complex expressions (which are supported by some frameworks).\r\n\r\nHere's another idea, inspired by C# Expression trees. This is just a rough idea, nothing fully thought-out! Syntax is terrible. I just want to see if this inspires someone.\r\n\r\nAssume we have a special kind of strings to denote expressions. \r\nLet's call it `type Expr<T, U> = string`.\r\nWhere `T` is the starting object type and `U` is the result type.\r\n\r\nAssume we could create an instance of `Expr<T,U>` by using a lambda that takes one parameter of type `T` and performs a member access on it. \r\nFor example: `person => person.address.city`. \r\nWhen this happens, the whole lambda is compiled to a string containing whatever access on the parameter was, in this case: `\"address.city\"`. \r\n\r\nYou can use a plain string instead, which would be seen as `Expr<any, any>`.\r\n\r\nHaving this special `Expr` type in the language enables stuff like that:\r\n```typescript\r\nfunction pluck<T, U>(array: T[], prop: Expr<T, U>): U[];\r\n\r\nlet numbers = pluck([{x: 1}, {x: 2}], p => p.x);  // number[]\r\n// compiles to:\r\n// let numbers = pluck([..], \"x\");\r\n```\r\n\r\nThis is basically a limited form of what Expressions are used in C# for. \r\nDo you think this could be refined and lead somewhere interesting?"}}