{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6606","repository_url":"https://api.github.com/repos/Microsoft/TypeScript","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6606/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6606/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6606/events","html_url":"https://github.com/Microsoft/TypeScript/issues/6606","id":128486808,"number":6606,"title":"Proposal: Get the type of any expression with typeof","user":{"login":"yortus","id":1252361,"avatar_url":"https://avatars3.githubusercontent.com/u/1252361?v=3","gravatar_id":"","url":"https://api.github.com/users/yortus","html_url":"https://github.com/yortus","followers_url":"https://api.github.com/users/yortus/followers","following_url":"https://api.github.com/users/yortus/following{/other_user}","gists_url":"https://api.github.com/users/yortus/gists{/gist_id}","starred_url":"https://api.github.com/users/yortus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yortus/subscriptions","organizations_url":"https://api.github.com/users/yortus/orgs","repos_url":"https://api.github.com/users/yortus/repos","events_url":"https://api.github.com/users/yortus/events{/privacy}","received_events_url":"https://api.github.com/users/yortus/received_events","type":"User","site_admin":false},"labels":[{"id":117630704,"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Accepting%20PRs","name":"Accepting PRs","color":"009800","default":false},{"id":265375236,"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Effort:%20Difficult","name":"Effort: Difficult","color":"d10401","default":false},{"id":113971155,"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Suggestion","name":"Suggestion","color":"006b75","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/Microsoft/TypeScript/milestones/2","html_url":"https://github.com/Microsoft/TypeScript/milestone/2","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://avatars0.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":298,"closed_issues":116,"state":"open","created_at":"2014-07-14T23:24:58Z","updated_at":"2017-06-13T01:32:24Z","due_on":null,"closed_at":null},"comments":54,"created_at":"2016-01-25T08:31:24Z","updated_at":"2017-06-13T11:59:40Z","closed_at":null,"body":"## Working Implementation for this Proposal\n\nTry it out: `npm install yortus-typescript-typeof`\n\nView the diff: [here](https://github.com/yortus/TypeScript/compare/master...yortus:type-query-powerup).\n## Problem Scenario\n\nTypeScript's type inference covers most cases very well. However there remain some situations where there is no obvious way to reference an anonymous type, even though the compiler is able to infer it. Some examples:\n##### I have a strongly-typed collection but the element type is anonymous/unknown, how can I reference the element type? (#3749)\n\n``` typescript\n// Mapping to a complex anonymous type. How to reference the element type?\nvar data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));\nfunction checkItem(item: typeof data[0] /* ERROR */) {...}\n\n// A statically-typed dictionary. How to reference a property type?\nvar things = { 'thing-1': 'baz', 'thing-2': 42, ... };\ntype Thing2Type = typeof things['thing-2']; // ERROR\n\n// A strongly-typed collection with special indexer syntax. How to reference the element type?\nvar nodes = document.getElementsByTagName('li');\ntype ItemType = typeof nodes.item(0); // ERROR\n```\n##### A function returns a local/anonymous/inaccessible type, how can I reference this return type? (#4233, #6179, #6239)\n\n``` typescript\n// A factory function that returns an instance of a local class\nfunction myAPIFactory($http: HttpSvc, id: number) {\n    class MyAPI {\n        constructor(http) {...}\n        foo() {...}\n        bar() {...}\n        static id = id;\n    }\n    return new MyAPI($http);\n}\nfunction augmentAPI(api: MyAPI /* ERROR */) {...}\n```\n##### I have an interface with a complex anonymous shape, how can I refer to the types of its properties and sub-properties? (#4555, #4640)\n\n``` typescript\n// Declare an interface DRY-ly and without introducing extra type names\ninterface MyInterface {\n    prop1: {\n        big: {\n            complex: {\n                anonymous: { type: {} }\n            }\n        }\n    },\n\n    // prop2 shares some structure with prop1\n    prop2: typeof MyInterface.prop1.big.complex; // ERROR\n}\n```\n## Why do we need to Reference Anonymous/Inferred Types?\n\nOne example is declaring a function that takes an anonymous type as a parameter. We need to reference the type somehow in the parameter's type annotation, otherwise the parameter will have to be typed as `any`.\n## Current Workarounds\n\nDeclare a dummy variable with an initializer that infers the desired type **without** evaluating the expression (this is important because we don't want runtime side-effects, just type inference). For example:\n\n``` typescript\nlet dummyReturnVal = null && someFunction(0, ''); // NB: someFunction is never called!\nlet ReturnType = typeof dummyReturnVal;           // Now we have a reference to the return type\n```\n\nThis workaround has a few drawbacks:\n- very clearly a kludge, unclear for readers\n- identifier pollution (must introduce a variable like `dummyReturnValue`)\n- does't work in ambient contexts, because it requires an imperative statement\n## Proposed Solution\n\n_(NB: This solution was already suggested in #4233, but that issue is tagged 'Needs Proposal', and there are several other closely related issues, hence this separate issue.)_\n\nAllow `typeof`'s operand to be an arbitrary expression. This is already allowed for `typeof expr` in a value position like `if (typeof foo() === 'string')`. But this proposal also allows an arbitrary expression when `typeof` is used in a type position as a type query, eg `type ElemType = typeof list[0]`.\n\nThis proposal already aligns closely with the current wording of the spec: \n\n> Type queries are useful for capturing anonymous types that are generated by various constructs such as object literals, function declarations, and namespace declarations.\n\nSo this proposal is just extending that usefulness to the currently unserved situations like in the examples above.\n## Syntax and Semantics\n\nThe semantics are exactly as already stated in the [spec 4.18.6](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#4.18.6):\n\n> The 'typeof' operator takes an operand of any type and produces a value of the String primitive type. In positions where a type is expected, 'typeof' can also be used in a type query (section 3.8.10) to produce the type of an expression.\n\nThe proposed difference relates to [section 3.8.10](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3.8.10) quoted below, where the struck-through text would be removed and the bold text added:\n\n> A type query consists of the keyword typeof followed by <strike>an expression. The expression is restricted to a single identifier or a sequence of identifiers separated by periods. The expression is processed as an identifier expression (section 4.3) or property access expression (section 4.13)</strike> **a unary expression**, the widened type (section 3.12) of which becomes the result. Similar to other static typing constructs, type queries are erased from the generated JavaScript code and add no run-time overhead.\n\nA point that must be emphasized (which I thought was also in the spec but can't find it) is that type queries **do not** evaluate their operand. That's true currently and would remain true for more complex expressions.\n\nThis proposal doesn't introduce any novel syntax, it just makes `typeof` less restrictive in the types of expressions it can query.\n## Examples\n\n``` typescript\n// Mapping to a complex anonymous type. How to reference the element type?\nvar data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));\nfunction checkItem(item: typeof data[0]) {...} // OK: item type is {raw:number, square:number}\n\n// A statically-typed dictionary. How to reference a property type?\nvar things = { 'thing-1': 'baz', 'thing-2': 42, ... };\ntype Thing2Type = typeof things['thing-2']; // OK: Thing2Type is number\n\n// A strongly-typed collection with special indexer syntax. How to reference the element type?\nvar nodes = document.getElementsByTagName('li');\ntype ItemType = typeof nodes.item(0); // OK: ItemType is HTMLLIElement\n\n// A factory function that returns an instance of a local class\nfunction myAPIFactory($http: HttpSvc, id: number) {\n    class MyAPI {\n        constructor(http) {...}\n        foo() {...}\n        bar() {...}\n        static id = id;\n    }\n    return new MyAPI($http);\n}\ntype MyAPI = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type\nfunction augmentAPI(api: MyAPI) {...} // OK\n\n// Declare an interface DRY-ly and without introducing extra type names\ninterface MyInterface {\n    prop1: {\n        big: {\n            complex: {\n                anonymous: { type: {} }\n            }\n        }\n    },\n\n    // prop2 shares some structure with prop1\n    prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}\n}\n```\n## Discussion of Pros/Cons\n\n**Against**: Poor syntax aesthetics. Alternative syntaxes addressing individual cases have been suggested in #6179, #6239, #4555 and #4640.\n\n**For**: Other syntaxes may look better for their specific cases, but they are all different from each other and each only solve one specific problem. This proposal solves the problems raised in all those issues, and the developer doesn't need to learn any new syntax(es).\n\n**Against**: An expression in a type position is confusing.\n\n**For**: TypeScript already overloads `typeof` with two meanings, as a type query it already accepts an expression in a type position and gets its type without evaluating it. This just relaxes the constraints on what that expression can be so that it can solve the problems raised in this issue.\n\n**Against**: This could be abused to write huge long multi-line type queries.\n\n**For**: There's no good reason to do that in a type query, but there are good reasons to allow more complex expressions. This is basically Martin Fowler's [enabling vs directing](http://martinfowler.com/bliki/SoftwareDevelopmentAttitude.html).\n## Design Impact, Questions, and Further Work\n##### Compatibility\n\nThis is a purely backward-compatible change. All existing code is unaffected. Using the additional capabilities of `typeof` is opt-in.\n##### Performance\n\nLooking at the diff you can see the changes are very minor. The compiler already knows the types being queried, this just surfaces them to the developer. I would expect negligable performance impact, but I don't know how to test this.\n##### Tooling\n\nI have set up VS Code to use a version of TypeScript with this proposal implemented as its language service, and all the syntax highlighting and intellisense is flawless as far as I have tested it.\n##### Complex expressions may occur in `.d.ts` files\n\n`typeof`'s operand could be any expression, including an IIFE, or a class expression complete with method bodies, etc. I can't think of any reason to do that, it's just no longer an error, even inside  a`.d.ts` file (`typeof` can be used - and is useful - in ambient contexts). So a consequence of this proposal is that \"statements cannot appear in ambient contexts\" is no longer strictly true.\n##### Recursive types are handled robustly\n\nThe compiler seems to already have all the logic in place needed to deal with things like this:\n\n``` typescript\nfunction foo<X,Y>(x: X, y: Y) {\n    var result: typeof foo(x, y); // ERROR: 'result' is referenced in its own type annotation\n    return result;\n}\n```\n##### Can query the return type of an overloaded function\n\nIt is not ambiguous; it picks the overload that matches the query's expression:\n\n``` typescript\ndeclare function foo(a: boolean): string;\ndeclare function foo(a: number): any[];\ntype P = typeof foo(0);    // P is any[]\ntype Q = typeof foo(true); // Q is string\n```\n"},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/308093668","html_url":"https://github.com/Microsoft/TypeScript/issues/6606#issuecomment-308093668","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/6606","id":308093668,"user":{"login":"atrauzzi","id":115199,"avatar_url":"https://avatars0.githubusercontent.com/u/115199?v=3","gravatar_id":"","url":"https://api.github.com/users/atrauzzi","html_url":"https://github.com/atrauzzi","followers_url":"https://api.github.com/users/atrauzzi/followers","following_url":"https://api.github.com/users/atrauzzi/following{/other_user}","gists_url":"https://api.github.com/users/atrauzzi/gists{/gist_id}","starred_url":"https://api.github.com/users/atrauzzi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/atrauzzi/subscriptions","organizations_url":"https://api.github.com/users/atrauzzi/orgs","repos_url":"https://api.github.com/users/atrauzzi/repos","events_url":"https://api.github.com/users/atrauzzi/events{/privacy}","received_events_url":"https://api.github.com/users/atrauzzi/received_events","type":"User","site_admin":false},"created_at":"2017-06-13T11:59:40Z","updated_at":"2017-06-13T11:59:40Z","body":"Time for this to happen!"}}
{"actor":{"display_login":"atrauzzi"}}