40

I am currently working on a TypeScript API, which requires some additional features binding to the Object prototype (Object.prototype).

Consider the following code:

class Foo {
    
}

interface Object {
    GetFoo(): Foo;
    GetFooAsString(): string;
}

//This is problematic...
Object.prototype.GetFoo = function() {
    return new Foo();
    // Note, this line is just for testing...I don't want my function to just return a blank instance of Foo!
}

//This is ok.
Object.prototype.GetFooAsString = function () {
    return this.GetFoo().toString();
}

You might want to try this directly at the Playground.

As you can see, I have a class called Foo (not the actual object name I will be using). I have also extended the Object interface to include two new functions. Finally I have implemented the functions against the prototype (these work in pure JavaScript, it's just TypeScript that complains).

Where I have annotated "//this is problematic..." TypeScript highlights this with a red squiggly, and shows the following error:

Cannot convert '() => Foo' to '{ (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; }': Call signatures of types '() => Foo' and '{ (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; (): Foo; }' are incompatible
() => Foo

Either this is just a TypeScript bug (I know it's still in development phase, so a lot of the bugs need ironing out, and I have illustrated some of these on CodePlex already), or, I'm missing something.

Why am I getting this issue?

If it's not a TypeScript bug, how can I fix this?

user3840170
  • 26,597
  • 4
  • 30
  • 62
Matthew Layton
  • 39,871
  • 52
  • 185
  • 313

3 Answers3

26

I used to have:

// See if an array contains an object
Array.prototype.contains = function (obj) {
    var i = this.length;
    while (i--) {
        if (this[i] === obj) {
            return true;
        }
    }
    return false;
}

in order to make that code compile with typescript I added the line:

interface Array {
    contains(obj: Object): boolean;
}

Thanks basarat!

Community
  • 1
  • 1
Tono Nam
  • 34,064
  • 78
  • 298
  • 470
13

This bug is fixed in TS 0.9.0 alpha as you can see below: no error in Ts 0.9.0 alpha

The playground is still running 0.8.3.

This basically happens because methods on some key interfaces ( Object, Number, String ) etc get cached as a performance optimization.

If you run this. The first time it loads you will not see that error. Try It.

As soon as you make an edit to that code, the parser goes through the code again, and since it cached the old interface definition sees a duplicate function definition and then effectively blows up. The more edits you make to that file the more complicated the error statement will get.

basarat
  • 261,912
  • 58
  • 460
  • 511
8

I have extended the Array the same way and faced a big problem when s a party was using for i in ... to loop over it. Now you can't control every third party code and these bugs could get really annoying so I suggest a better aprocach:

interface Array<T> {
   crandom(): T;
}

/** Retrieve a random element from the list */
 Object.defineProperty(Array.prototype, 'crandom', { value: function() {

    let index = Math.floor(Math.random() * this.length);

    return this[index];
}
});

Now by using Object.defineProperty your new property won't be enumerated over and it is safe. The above code pretty much gives a random element from the array. I made another one too which pops a random element from array:

Object.defineProperty(Array.prototype, 'popRandom', { value: function() {

    let index = Math.floor(Math.random() * this.length);

    let result = this[index];
    this.splice(index, 1);

    return result;
}
});

with Object.defineProperty You get more control over this creation and you can add additional restrictions too.

Arijoon
  • 2,184
  • 3
  • 24
  • 32
  • Exactly so. It allows you to control its enumeration and immutability, however adding or overwriting functionality on existing objects (like Array, String, Number, etc) is frowned upon and discouraged in the community. You might find that your code works when using defineProperty and writable: false because 3rd party code can't overwrite your code – Matthew Layton May 02 '17 at 23:03
  • 1
    Good point, I disagree with it being frowned upon as extending base classes could be very nice in cases where functionality should be there but isn't (example a `random` method on an array). As long as you aren't writing a library to be used by others, I personally don't see a problem with extending bases, given that it is used in moderation and not overused to high hell. – Arijoon May 03 '17 at 15:37
  • what happens when the ECMAScript board approve standards to implement new functionally on those objects, but you've already added your own functionality with the same name and a different behaviour? All of a sudden your library dies because it's not compliant. PrototypeJS is a famous example of this – Matthew Layton May 03 '17 at 15:41
  • Exactly why I mentioned making a library that way isn't good, and I name my methods with a prefix like `crandom` instead of `random` – Arijoon May 03 '17 at 19:01