{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12114","repository_url":"https://api.github.com/repos/Microsoft/TypeScript","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12114/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12114/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12114/events","html_url":"https://github.com/Microsoft/TypeScript/pull/12114","id":188141684,"number":12114,"title":"Mapped types","user":{"login":"ahejlsberg","id":4226954,"avatar_url":"https://avatars.githubusercontent.com/u/4226954?v=3","gravatar_id":"","url":"https://api.github.com/users/ahejlsberg","html_url":"https://github.com/ahejlsberg","followers_url":"https://api.github.com/users/ahejlsberg/followers","following_url":"https://api.github.com/users/ahejlsberg/following{/other_user}","gists_url":"https://api.github.com/users/ahejlsberg/gists{/gist_id}","starred_url":"https://api.github.com/users/ahejlsberg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ahejlsberg/subscriptions","organizations_url":"https://api.github.com/users/ahejlsberg/orgs","repos_url":"https://api.github.com/users/ahejlsberg/repos","events_url":"https://api.github.com/users/ahejlsberg/events{/privacy}","received_events_url":"https://api.github.com/users/ahejlsberg/received_events","type":"User","site_admin":false},"labels":[{"id":164593272,"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/cla-not-required","name":"cla-not-required","color":"009800","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":17,"created_at":"2016-11-09T00:33:11Z","updated_at":"2016-11-11T00:59:15Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/Microsoft/TypeScript/pulls/12114","html_url":"https://github.com/Microsoft/TypeScript/pull/12114","diff_url":"https://github.com/Microsoft/TypeScript/pull/12114.diff","patch_url":"https://github.com/Microsoft/TypeScript/pull/12114.patch"},"body":"This PR introduces *Mapped Types*, a new kind of object type that maps a type representing property names over a property declaration template. In combination with *index types* and *indexed access types* (#11929), mapped types enable a number of interesting type transformations. In particular, mapped types enable more accurate typing of intrinsic functions such as `Object.assign` and `Object.freeze` as well as APIs that map or transform shapes of objects.\r\n\r\nA mapped type takes one of the forms\r\n\r\n```ts\r\n{ [ P in K ] : T }\r\n{ [ P in K ] ? : T }\r\n{ readonly [ P in K ] : T }\r\n{ readonly [ P in K ] ? : T }\r\n```\r\n\r\nwhere `P` is an identifier, `K` is a type that must be assignable to `string | number`, and `T` is some type that can use `P` as a type parameter. A mapped type resolves to an object type with a set of properties constructed by introducing a type parameter `P` and iterating it over the constituent types in `K`, for each such `P` declaring a property or index signature with the type given by `T` (which possibly references `P` as a type parameter). When `P` is a string or numeric literal type, a property with that name is introduced. Otherwise, when `P` is type `string` or `number`, an index signature is introduced.\r\n\r\n```ts\r\ntype Item = { a: string, b: number, c: boolean };\r\n\r\ntype T1 = { [P in \"x\" | \"y\"]: number };  // { x: number, y: number }\r\ntype T2 = { [P in \"x\" | \"y\"]: P };  // { x: \"x\", y: \"y\" }\r\ntype T3 = { [P in \"a\" | \"b\"]: Item[P] };  // { a: string, b: number }\r\ntype T4 = { [P in keyof Item]: Date };  // { a: Date, b: Date, c: Date }\r\ntype T5 = { [P in keyof Item]: Item[P] };  // { a: string, b: number, c: boolean }\r\ntype T6 = { readonly [P in keyof Item]: Item[P] };  // { readonly a: string, readonly b: number, readonly c: boolean }\r\ntype T7 = { [P in keyof Item]: Array<Item[P]> };  // { a: string[], b: number[], c: boolean[] }\r\n```\r\n\r\nSome examples of interesting type operators declared using mapped types:\r\n\r\n```ts\r\n// Make all properties in T optional\r\ntype Partial<T> = {\r\n    [P in keyof T]?: T[P];\r\n};\r\n\r\n// Make all properties in T readonly\r\ntype Readonly<T> = {\r\n    readonly [P in keyof T]: T[P];\r\n};\r\n\r\n// Pick a set of properties from T\r\ntype Pick<T, K extends keyof T> = {\r\n    [P in K]: T[P];\r\n}\r\n\r\n// A type with a given set of properties of a given type\r\ntype Record<K extends string | number, T> = {\r\n    [_ in K]: T;\r\n}\r\n\r\n// A proxy for a given type\r\ntype Proxy<T> = {\r\n    get(): T;\r\n    set(value: T): void;\r\n}\r\n\r\n// Proxify all properties in T\r\ntype Proxify<T> = {\r\n    [P in keyof T]: Proxy<T[P]>;\r\n}\r\n```\r\n\r\nSome functions that use the above types:\r\n\r\n```ts\r\nfunction assign<T>(obj: T, props: Partial<T>): void;\r\nfunction freeze<T>(obj: T): Readonly<T>;\r\nfunction pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;\r\nfunction mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>;\r\nfunction proxify<T>(obj: T): Profixy<T>;\r\n```\r\n\r\nAnd some code that uses the functions:\r\n\r\n```ts\r\ninterface Shape {\r\n    name: string;\r\n    width: number;\r\n    height: number;\r\n    visible: boolean;\r\n}\r\n\r\nfunction f1(s1: Shape, s2: Shape) {\r\n    assign(s1, { name: \"circle\" });\r\n    assign(s2, { width: 10, height: 20 });\r\n}\r\n\r\nfunction f2(shape: Shape) {\r\n    const frozen = freeze(shape);\r\n    frozen.name = \"circle\";  // Error, name is read-only\r\n}\r\n\r\nfunction f3(shape: Shape) {\r\n    const x = pick(shape, \"name\", \"visible\");  // { name: string, visible: boolean }\r\n}\r\n\r\nfunction f4() {\r\n    const rec = { foo: \"hello\", bar: \"world\", baz: \"bye\" };\r\n    const lengths = mapObject(rec, s => s.length);  // { foo: number, bar: number, baz: number }\r\n}\r\n\r\nfunction f5(shape: Shape) {\r\n    const p = proxify(shape);\r\n    let name = p.name.get();\r\n    p.visible.set(false);\r\n}\r\n```\r\n\r\nThe `mapObject` example above shows how type inference can be used for mapped types. When inferring from an object type `S` to a mapped type `{ [P in K]: T }`, `keyof S` is inferred for `K` and `S[keyof S]` is inferred for `T`. In other words, a literal union type of all property *names* in `S` is inferred for `K` and a union of all property *types* in `S` is inferred for `T`.\r\n\r\nRelated issues include #1295, #2710, #4889, #6613, #10725, #11100, #11233.\r\n\r\nThe PR still needs tests."},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/259853302","html_url":"https://github.com/Microsoft/TypeScript/pull/12114#issuecomment-259853302","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/12114","id":259853302,"user":{"login":"fredgalvao","id":616464,"avatar_url":"https://avatars.githubusercontent.com/u/616464?v=3","gravatar_id":"","url":"https://api.github.com/users/fredgalvao","html_url":"https://github.com/fredgalvao","followers_url":"https://api.github.com/users/fredgalvao/followers","following_url":"https://api.github.com/users/fredgalvao/following{/other_user}","gists_url":"https://api.github.com/users/fredgalvao/gists{/gist_id}","starred_url":"https://api.github.com/users/fredgalvao/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fredgalvao/subscriptions","organizations_url":"https://api.github.com/users/fredgalvao/orgs","repos_url":"https://api.github.com/users/fredgalvao/repos","events_url":"https://api.github.com/users/fredgalvao/events{/privacy}","received_events_url":"https://api.github.com/users/fredgalvao/received_events","type":"User","site_admin":false},"created_at":"2016-11-11T00:59:15Z","updated_at":"2016-11-11T00:59:15Z","body":"Actually it seems like a mix of `Proxy`, `Record`, and the `mapObject` examples.\r\n\r\n```ts\r\nclass FieldBox<P> {\r\n  value: P;\r\n}\r\n\r\ninterface ObjectBox<T> {\r\n  [K in keyof T]: FieldBox<T[K]>;\r\n}\r\n\r\nclass Bacon {\r\n  isPerfect: boolean;\r\n}\r\n\r\nfunction unbox<T>(box: ObjectBox<T>):T {\r\n  return mapObject(box, fieldBox => fieldBox.value);\r\n}\r\n\r\nfunction box<T>(object: T): ObjectBox<T> {\r\n  return  mapObject(object, field => boxField(field));\r\n}\r\n\r\nfunction boxField<P>(value: P): FieldBox<P> {\r\n  let fieldBox = new FieldBox()\r\n  fieldBox.value = value;\r\n  return fieldBox;\r\n}\r\n\r\nlet bacon = new Bacon();\r\nbacon.isPerfect = true;\r\n\r\nlet baconInBox = box(bacon);\r\nbaconInBox.isPerfect.value === true\r\n\r\nlet freshBaconOutOfTheBox = unbox(baconInBox);\r\nfreshBaconOutOfTheBox.isPerfect === true\r\n```\r\n\r\nDepending on your implementation of the `mapObject` function, I think you could lose the shell type for the argument between calls, but the overall interface would still comply all the way.\r\nI wish we had a typescript playground that allowed us to pick what tsc version we want, so I could test this right now..."}}
{"actor":{"display_login":"fredgalvao"}}