镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Suggestion: Using typeof with an expression #4233

Description

@Andael

It would be helpful if the typeof keyword were changed to support more complex expressions, such as function calls.

Example

Given the following AngularJS service definition:

module myApp {
  function myApiFactory($http: HttpSvc) {
    return {
      getSetting: (key: string) => $http.get(etc),
    };
  }
  angular.service("myApi", myApiFactory);
}

It would be good if the following statement were possible:

export type MyApi = typeof myApiFactory();

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 typeof keyword. However, it significantly reduces readability and clarity. An example:

if (false)
  var obj = myApiFactory(null);
export type MyApi = typeof obj;

Activity

  1. lazdmx commented on Aug 8, 2015

    @lazdmx

    👍 Seems related to #3749 (comment) as means to get collection element type through expression.

  2. mhegazy commented on Aug 8, 2015

    @mhegazy
    Contributor

    I find Using an expression with potential sides effects in a type position really confusing.

  3. mhegazy commented on Aug 8, 2015

    @mhegazy
    Contributor

    Also can you elaborate on how the API and the type would be used outside the module?

  4. yortus commented on Aug 8, 2015

    @yortus
    Contributor

    I have found myself wishing this were possible sometimes - usually when using type definitions from DefinitelyTyped. For example, knex.d.ts internally defines a QueryBuilder interface but does not export it, even though most knex methods return instances of this type. That makes it a pain to, say, write a function that takes a QueryBuilder as 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 typeof on that. In the knex case, 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.ts and sumbit a PR to DefinitelyTyped, but that's a 'big fix' approach that may not always be possible, and for which typeof expr could provide a nice short-term fix in the meantime.

  5. yortus commented on Aug 8, 2015

    @yortus
    Contributor

    This would be equivalent of C++'s decltype(expr), which is used in a type position but does not evaluate expr. It's a very useful facility for writing reuseable code that propagates types without foreknowledge of their names (or shapes).

  6. Andael commented on Aug 9, 2015

    @Andael
    Author

    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?

  7. Andael commented on Aug 9, 2015

    @Andael
    Author

    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) {
        // ...
    }
  8. mhegazy commented on Aug 9, 2015

    @mhegazy
    Contributor

    i would rather have a compiler operator e.g. __returnTypeof than to overload typeof.

  9. yortus commented on Aug 9, 2015

    @yortus
    Contributor

    If this facility existed as a purely compile-time thing, say __returnTypeof expr or decltype(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?

  10. added
    SuggestionAn idea for TypeScript
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Aug 10, 2015
  11. rotemdan commented on Sep 10, 2015

    @rotemdan

    I think that the scope of typeof expression is 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 expression would be commonly used (such as, say, typeon or returnTypeOf [and perhaps also returnTypeOn?] ) 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>;
  12. benliddicott commented on Sep 10, 2015

    @benliddicott

    Can I add my vote to this item. This seems very useful and has wide application.

  13. d180cf commented on Sep 11, 2015

    @d180cf

    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.

  14. rotemdan commented on Sep 11, 2015

    @rotemdan

    d180cf

    I think using a "virtual" sub-type/property is an interesting approach, and I'm also investigating alternatives to returnTypeOf and returnTypeOn myself (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.ReturnType would be ambiguous.

    Using it with my proposed typeon operator, (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.

  15. basarat commented on Sep 14, 2015

    @basarat
    Contributor

    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;
  16. rotemdan commented on Sep 14, 2015

    @rotemdan

    Basarat Ali Syed (@basarat)

    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 totypeof func(). It can still be used to emulate typeon 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
    }
  17. benliddicott commented on Sep 14, 2015

    @benliddicott

    See also Generic Meta Types #3779

  18. yortus commented on Sep 14, 2015

    @yortus
    Contributor

    Basarat Ali Syed (@basarat) Rotem Dan (@rotemdan) At present typeof is useable in ambient contexts (e.g. in .d.ts files), and perhaps the extended typeof proposed here could preserve this quality. That's one thing the workarounds, consisting of imperative statements of various forms, cannot do.

  19. msklvsk commented on Dec 19, 2015

    @msklvsk

    Dear Santa, please bring me decltype in Typescript.

  20. yortus commented on Jan 25, 2016

    @yortus
    Contributor

    I submitted a proposal + implementation for this: #6606

  21. added
    DeclinedThe issue was declined as something which matches the TypeScript vision
    and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Jan 25, 2016
  22. RyanCavanaugh commented on Jan 25, 2016

    @RyanCavanaugh
    Member

    Let's take the discussion over to the nice clean slate at #6606.

  23. magnushiie commented on Jun 16, 2017

    @magnushiie
    Contributor

    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.

  24. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions