Repository navigation
Suggestion: Using typeof with an expression #4233
Description
Activity
👍 Seems related to #3749 (comment) as means to get collection element type through expression.
I find Using an expression with potential sides effects in a type position really confusing.
Also can you elaborate on how the API and the type would be used outside the module?
I have found myself wishing this were possible sometimes - usually when using type definitions from DefinitelyTyped. For example,
knex.d.tsinternally defines aQueryBuilderinterface but does not export it, even though mostknexmethods return instances of this type. That makes it a pain to, say, write a function that takes aQueryBuilderas an argument.A workaround is to create a 'dummy' variable with the desired type using an expression that is not necessarily evaluated, and then using
typeofon that. In theknexcase, the following works:import Knex = require('knex'); var dummyQueryBuilder = false ? Knex(<any>{})() : null; type QueryBuilder = typeof dummyQueryBuilder;
The proposal outlined by @ander-nz is far cleaner than this, although I agree with Mohamed Hegazy (@mhegazy) that putting expressions in type positions may be confusing.
The best approach in this example is to fix
knex.d.tsand sumbit a PR to DefinitelyTyped, but that's a 'big fix' approach that may not always be possible, and for whichtypeof exprcould provide a nice short-term fix in the meantime.This would be equivalent of C++'s
decltype(expr), which is used in a type position but does not evaluateexpr. It's a very useful facility for writing reuseable code that propagates types without foreknowledge of their names (or shapes).Mohamed Hegazy (@mhegazy) I agree that the syntax could be confusing, as per below:
var a = typeof myApiFunc(); // actually invokes myApiFunc, and then sets a to "object". type A = typeof myApiFunc(); // does not invoke it, only looks at its return type.
A possible alternate syntax is using
<``>brackets, since these are already used by TypeScript. For example:type MyApi = typeof<myApiFunc()>;
This could simplify the syntax, and would look out of place enough to remind the reader that the expression is a TypeScript feature. Does this look clearer?
Mohamed Hegazy (@mhegazy) And to answer your question about how the type would be used outside the module. That example is designed for AngularJS, which uses dependency injection, such as:
function MyCtrl($scope, myApi: myApp.MyApi) { // ... }
i would rather have a compiler operator e.g.
__returnTypeofthan to overloadtypeof.If this facility existed as a purely compile-time thing, say
__returnTypeof exprordecltype(expr)or whatever, could it be used in an ambient context? E.g:declare module "mymodule" { import knex = require('knex'); type QueryBuilder = __returnTypeof (knex(<any>{})()); // obtain the QueryBuilder type from knex; export function someExportedFunction(query: QueryBuilder): void; }
This covers a likely use case for the construct, but would involve allowing expressions to appear in ambient declarations. Is that feasible with the design of the compiler?
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Aug 10, 2015 I think that the scope of
typeof expressionis very wide. It is very powerful but that also means that expressing some specific patterns in it would require sacrificing tidiness and clarity which would lead to lower general usability in those scenarios.Perhaps it would be beneficial to try to isolate the most prominent cases where
typeof expressionwould be commonly used (such as, say,typeonorreturnTypeOf[and perhaps alsoreturnTypeOn?] ) and try to individually consider a more "optimized", special-purpose syntax and semantics for each one of them.It would still be useful to have a more powerful operator for special cases, however, it might be possible to emulate many of them by embedding the evaluated expression in a function and "extracting" the inferred return type, like this:
function expressionFunc() { return [...] // expression } // can also be written as 'expressionFunc = () => [..]' var x: returnTypeOf expressionFunc;
This would naturally extend to generic expressions as well:
function genericExpressionFunc<T>() { return [...] // generic expression } var x: returnTypeOf expressionFunc<string>;
Can I add my vote to this item. This seems very useful and has wide application.
Why not to introduce the notion of subtypes?
function f(x: number, y: string) { return x || y; } type T = (typeof f).ReturnType; // string | number
Every function type could have a .ReturnType member that would be equal to the type of what the function returns. Typescript knows this type, but doesn't seem to make it accessible in a straightforward way.
I think using a "virtual" sub-type/property is an interesting approach, and I'm also investigating alternatives to
returnTypeOfandreturnTypeOnmyself (including even syntax that would handle overloaded return signatures etc.). The problem with this particular syntax is that Typescript (and Javascript) allows declaring hybrid function/object types (functions with normal properties defined on them):interface HybridFunc { // This is a function signature specifier: (x: number, y: string): string; // This is an example of a normal property with type 'Array<boolean>' // that just happens to have the name 'ReturnType': ReturnType: Array<boolean>; } let hybridFunc: HybridFunc;
So in this case type
type T = typeof hybridFunc.ReturnTypewould be ambiguous.Using it with my proposed
typeonoperator, (an operator that would allow referencing a property type directly through its containing type): would also have the same problem:type T = typeon HybridFunc.ReturnType.Going though the issue. I suggest that a simpler workaround be used:
function myApiFactory() { return { getSetting: () => null }; } const myApiFactoryReturn = false && myApiFactory(); export type MyApi = typeof myApiFactoryReturn;
Basically instead of
export type MyApi = typeof myApiFactory();
You have two lines:
const myApiFactoryReturn = false && myApiFactory(); type MyApi = typeof myApiFactoryReturn;
I've found an even simpler workaround yesterday:
// This expression would never execute but its type would be inferred: let e = true ? undefined : ...expression...; type ExpressionType = typeof e;
To get return types:
let r = true ? undefined : someFunction(); type RerturnType = typeof r;
This workaround would only work on runtime code positions though, so it is mostly relevant to
typeof func(). It can still be used to emulatetypeon this.func()in some cases, but that would be more complex:interface A { func(x: number): string; prop: ReturnType; // emulates 'typeon this.func()' } let a: A; let r = true ? undefined : a.func(0); type RerturnType = typeof r;
But similarly to other workarounds for
typeon: in the case of a generic interface or class it would not be possible to propagate the generic parameter into the dummy instance:interface A<T> { func(x: number): T; prop: typeon this.func(); // No workaround for this }
See also Generic Meta Types #3779
Basarat Ali Syed (@basarat) Rotem Dan (@rotemdan) At present
typeofis useable in ambient contexts (e.g. in.d.tsfiles), and perhaps the extendedtypeofproposed here could preserve this quality. That's one thing the workarounds, consisting of imperative statements of various forms, cannot do.Dear Santa, please bring me
decltypein Typescript.I submitted a proposal + implementation for this: #6606
- addedDeclinedThe issue was declined as something which matches the TypeScript visionThe issue was declined as something which matches the TypeScript visionand removedNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Jan 25, 2016 RyanCavanaugh commented
on Jan 25, 2016 MemberMore actionsLet's take the discussion over to the nice clean slate at #6606.
With recent null checks, the above recommendation:
let r = true ? undefined : someFunction(); type ReturnType = typeof r;can be changed to
let r = true ? undefined as never : someFunction(); type ReturnType = typeof r;to avoid the (undefined | ...) type.
- locked and limited conversation to collaborators
on Jun 19, 2018
It would be helpful if the
typeofkeyword were changed to support more complex expressions, such as function calls.Example
Given the following AngularJS service definition:
It would be good if the following statement were possible:
Explanation
Since the function is a factory rather than the actual service, this type alias would other components to have a strongly-typed reference to the service. Though this example relates to AngularJS' service factory and dependency injection, this particular change would not be solely for AngularJS support.
Existing solutions
One option to resolve this issue is to explicitly define an interface for the service. However, this solution would require repeating the signature for all of the service's methods.
A workaround exists that relies on JavaScript's scoping rules to satisfy TypeScript's
typeofkeyword. However, it significantly reduces readability and clarity. An example: