Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Syntax for constructor signature #4775

Closed
omidkrad opened this issue Sep 14, 2015 · 9 comments
Closed

Suggestion: Syntax for constructor signature #4775

omidkrad opened this issue Sep 14, 2015 · 9 comments
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds

Comments

@omidkrad
Copy link

This is a valid signature:

    subscribe<T>(event: { new(...any): T; }, callback: (message: T) => void): void;
                        ^^^^^^^^^^^^^^^^^^^

I would like to suggest to support typeof T to be equivalent of { new(...any): T; } above.

    subscribe<T>(event: typeof T, callback: (message: T) => void): void;
                        ^^^^^^^^

which in my opinion is much more expressive. Notice that if we used typeof SomeClass compiler would accept it. So why not also accept typeof T?

Here's an example:

declare class EventAggregator
{
    subscribe<T>(event: typeof T, callback: (message: T) => void): void;
//  subscribe<T>(event: { new(...any): T; }, callback: (message: T) => void): void;
    publish<T>(event: T): void;
}

// class with zero argument constructor
class SomeMessage {
    constructor() {}
    data: string;
}

// class with argument constructor
class SomeMessage2 {
    constructor(public data: string) {}
}

var ea = new EventAggregator();
// CASE 1: okay - no error
ea.subscribe(SomeMessage, message => {
    console.log(message.data);
});

// CASE 2: okay - no error
ea.subscribe(SomeMessage2, message => {
    console.log(message.data);
});

// CASE 3: errors out - good, that is expected. I want the method to
// take only classes (types) and not instances of classes
ea.subscribe(new SomeMessage(), message => {
    console.log(message.data);
});

ea.publish(new SomeMessage());
Workarounds that don't work really

Using generic type of T extends Function or Function instead of T breaks type inference of T

subscribe<T extends Function>(event: T, callback: (message: T) => void): void;
          ^^^^^^^^^^^^^^^^^^
subscribe<T>(event: Function, callback: (message: T) => void): void;
                    ^^^^^^^^
@omidkrad omidkrad changed the title Suggestion: Syntax sugar for constructor signature Suggestion: Syntax for constructor signature Sep 14, 2015
@kitsonk
Copy link
Contributor

kitsonk commented Sep 14, 2015

What about constructors that have a defined argument signature?

@omidkrad
Copy link
Author

@kitsonk in that case you can use exact constructor signature like { new(a: string, b: string): T }.

@kitsonk
Copy link
Contributor

kitsonk commented Sep 14, 2015

Then that would seem "auto-magic" to me and surprising... "if you have this one signature you can use this one keyword in these scenarios, but if you have anything else..."

@ahejlsberg
Copy link
Member

The typeof operator in a type position is used to obtain the type of a variable. I think it would be really odd for it to also be notation for a constructor function type. Plus it would be a breaking change:

var x: number;
function foo<x>(b: typeof x) { }

I would suggest defining a generic type that represents a constructor:

interface Constructor<T> {
    new (...args: any[]): T;
}

declare class EventAggregator
{
    subscribe<T>(event: Constructor<T>, callback: (message: T) => void): void;
    publish<T>(event: Constructor<T>): void;
}

@ahejlsberg ahejlsberg added the Suggestion An idea for TypeScript label Sep 14, 2015
@omidkrad
Copy link
Author

@kitsonk that was the point of my suggestion, to define type of constructor with arbitrary arguments.

@ahejlsberg the Constructor interface you suggested is what I needed. Thank you! I noticed I had suggested this before in issue #204 but didn't come up with a solution I liked.

However, I wonder what typeof ClassX means below? Isn't it denoting the constructor type of ClassX? If the compiler is accepting typeof ClassX then why it's not complaining on foo(ClassY)?

class ClassX {}
class ClassY {}
function foo<x>(b: typeof ClassX) { }
foo(ClassX);
foo(ClassY);

@ahejlsberg
Copy link
Member

However, I wonder what typeof ClassX means below? Isn't it denoting the constructor type of ClassX? If the compiler is accepting typeof ClassX then why it's not complaining on foo(ClassY)?

You have no members in ClassX and ClassY so they are structurally equivalent. Add a member to each and you'll get an error:

class ClassX { x; }
class ClassY { y; }
function foo<x>(b: typeof ClassX) { }
foo(ClassX);
foo(ClassY);  // Error

@omidkrad
Copy link
Author

OK, that makes sense. Now what makes typeof ClassX valid below but not typeof T?

class ClassX { x; }
class ClassY { y; }
function foo(b: typeof ClassX, c: ClassX) { }
foo(ClassX, new ClassX);
function bar<T>(b: typeof T, c: T) { } // Error
bar(ClassY, new ClassY);

@ahejlsberg
Copy link
Member

typeof ClassX is valid because ClassX denotes a named value (i.e. the constructor function). It also denotes a type, but that's irrelevant here.

typeof T is an error because T doesn't denote a named value.

@ahejlsberg
Copy link
Member

An easy rule of thumb is: When typeof is used in a type position, the argument must be a valid expression and the operator obtains the type of that expression.

@danquirk danquirk added the Too Complex An issue which adding support for may be too complex for the value it adds label Sep 14, 2015
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds
Projects
None yet
Development

No branches or pull requests

4 participants