Support magic strings in inferface method signatures

Topics: Language Specification
Jan 4, 2013 at 11:55 AM

From what I have seem, so far TypeScript cannot support type information for most jquery plugins because they use strings as parameters for the main function.

$.plugin("doSomething");

As I understand that there are better ways to do this, there could be a simple way to support this without adding the feature to the whole language.

Interface definitions could to support having magic strings in the signature, so we can have typed information for existing plugins. Instead of:

interface JqueryPlugin {
	method(action : string): any;
}

We could have:

interface JqueryPlugin {
	method(action : string "getObject"): CustomType;
	method(action : string "runSomething"): number;
	method(action : string "doSomething"): void;
}

I believe that if applied only to interfaces could be a nice addition to the language and help support existing code.
Coordinator
Jan 4, 2013 at 4:48 PM

I agree that overloading based on constants would be very handy (we've actually talked about it a few times in design meetings).  In fleshing it out, there are a couple subtle points.

The compiler could give an error if you pass a string constant that isn't one listed in the overload set.  This doesn't quite work, as the user may pass something that isn't a constant, so we need to be sure that there is at least a default overload.

interface JqueryPlugin {
    method(action : "getObject"): CustomType;
    method(action : "runSomething"): number;
    method(action : "doSomething"): void;
    method(action : string) : string;
}

Things are a little weird when you allow them to be mixed.  We'd also need to require default overloads there, too:

interface JqueryPlugin {
    method(action : "getObject", data: string): CustomType;
    method(action : string, data: "myData"): number;
method(action : string, data: string): string; }

We'd also need to be careful that when doing contextual typing, we keep things simple enough that typechecking can stay pretty fast. 

All that said, it's probably doable and would be helpful for just the kinds of things you mention.

Jan 4, 2013 at 4:50 PM
Edited Jan 4, 2013 at 4:59 PM

The problem is that string values are a runtime properties, so difficult for the type checker to find out at compile time. However the good news is that you should be able to do something like this:

 

var e:Element2;
e.addListener(EventName.onMouseMove, function(ev) {});
e.addListener(EventName.onKeyDown, function(ev) {});


interface Element2 {
    addListener(name:MouseEventName, handler: (event:MouseEvent) => void);
    addListener(name:KeyboardEventName, handler: (event:KeyboardEvent) => void);
}

class EventName {
   static onMouseMove:MouseEventName = <MouseEventName><any>"onmousemove";	
   static onKeyUp:KeyboardEventName = <KeyboardEventName><any>"onkeyup";
   static onKeyDown:KeyboardEventName = <KeyboardEventName><any>"onkeydown";
}

interface MouseEventName{__a;};
interface KeyboardEventName{__b;};

// Peter

Jan 11, 2013 at 8:38 PM

I really liked the idea of "method(action : "getObject"): CustomType;". Looks better. 

Anyway, regarding Peter's suggestion, I don't think parsing variables would be a good idea, it would really complicate for the compiler, as the string could be the return of a function, etc. The idea is just to support existing code that use this pattern, so we can have code completion for everything correctly.

As for the default overload, after giving it some thought, I believe it is a good idea. Looking at this:

interface Foo {
     method("bar") : bool;
} 

class X implements Foo {
    method(arg : string) : bool { ... }
}

It looks a little bit strange without the default overload. Also, if you don't have it, would require the compiler to throw an error if the string doesn't match, and that would open the door for other parameter value checks, people would use it for strings and ask why it is not available for integers... better to keep it simple.

The following rules could be applied:

  • The value can only be a string literal
  • Only interfaces would be allowed to have this kind of signature
  • Must have a default overload without the values
  • Literal parameters must be the firtst parameters, never in the middle or in the end (opposite rule of optional parameters)

As for the speed, I don't think would make much difference if made only for strings and not all literals.

Jan 11, 2013 at 9:19 PM

I guess my post was not that clear. The solution in there already works today. Just cut&paste that piece of code in the playground to see it work. So there is no need to add any complexity to the compiler.

I do think however it might not be the easiest to read or understand. The literal solution does look nicer.

// Peter

 

 

Jan 15, 2013 at 8:22 PM
Edited Jan 15, 2013 at 8:24 PM

Maybe add typed values as more general solution? Something like that:

declare type JQueryEvent;

declare type JEventClick: JQueryEvent = 'click';

declare type JEventComplex: JQueryEvent = ['click', 'hover'];

interface JQuery {
    bind(event: JEventClick, fn: (e: ClickEvent) => any): JQuery;
    bind(event: JQueryEvent, fn: (e: Event) => any)): JQuery;
}

$('body').bind(JEventClick, (e) => console.log('click at ' + e.x)); // ok

$('body').bind(JEventComplex, (e) => console.log('some event')); // ok

$('body').bind(JQueryEvent, (e) => false); // compile time error - JQueryEvent don't has emmited value
Coordinator
Jan 16, 2013 at 3:35 PM

@irezvov - I'm not sure why the last line would be a compile time error.  The types look like they would match, unless I'm reading it wrong.

Jan 16, 2013 at 3:44 PM

JQueryEvent is a abstract phantom type here. We can use it only as "interface" for other typed values. 

$('body').bind(JEventClick, (e) => console.log('click at ' + e.x)); emited into $('body').bind('click', function(e) => { console.log('click at ' + e.x) });

but 

$('body').bind(JQueryEvent, (e) => false); in $('body').bind( , function(e) { return false; }); coz JQueryEvent doesn't have runtime value

Jan 18, 2013 at 12:56 AM

A feature like this would also work well for defining event signatures in nodejs. You bind to them using .on(eventName, handler), but the signature of the handler differs depending on which eventName you specify, e.g.

requestStream.on("data", function(data) { ... });
requestStream.on("end", function() { ... });
requestStream.on("error", function(exception) { ... });

Jan 29, 2013 at 6:01 PM

Hi,

Should I create a workitem for this to be considered for future releases?