Open
Description
Summary
To support both the semantics of subclassing built-ins in ES6 and still allow authors to augment built-ins, we need a mechanism to reopen the static and instance sides of a class.
Current state
Today we can re-open interfaces, allowing authors to augment built-ins (for example, to support polyfills):
// in lib.d.ts
interface Array<T> { /*...*/ }
interface ArrayConstructor { /*...*/ }
declare var Array: ArrayConstructor;
// in polyfill.ts
interface Array<T> {
includes(value: T): Boolean;
}
interface ArrayConstructor {
of<T>(...items: T[]): Array<T>;
}
Array.prototype.includes = function (value: any) { return this.indexOf(value) != -1; }
Array.of = function<T> (...items: T[]) { return items; }
We can also re-open the static side of a class, in a limited fashion:
// initial declaration
class MyClass {
}
// re-open
module MyClass {
export var staticProperty = 1;
}
There are several issues with these approaches:
- You cannot use type defined by the
var
/interface
pattern in theextends
clause of a class in TypeScript, meaning that "classes" defined using this pattern cannot be subclassed in ES6, which is an issue for built-ins. - While you can re-open the static side of a class using
module
, you can only use non-keyword identifiers for property names. So you could not, for example, add a[Symbol.species]
property to the class, or use decorators on these members. - There is no way to re-open the instance side of a class.
Proposal
I propose we add a new syntactic modifier for the class
declaration that would indicate we are re-opening an existing class. For this example I am using the keyword partial
, although the semantics here differ significantly than the same-named capability in C#:
// in lib.d.ts
declare class Array<T> {
}
// in polyfill.ts
partial class Array<T> {
static of<T>(...items: T[]) { return items; }
includes(value: T): boolean { return this.indexOf(value) != -1; }
}
// emit (ES5)
Array.of = function() {
var items = [];
for (var _i = 0; i < arguments.length; i++)
items[i] = arguments[i];
return items;
}
Array.prototype.includes = function(value) {
return this.indexOf(value) != -1;
}
Rules
- A
partial
class declaration must be preceded by a non-partial
class declaration in the same lexical scope. These should be the same rules that apply when merging a module with a class or function today. - A
partial
class declaration must have the same module visibility as the preceding non-partial
class declaration. - A
partial
class declaration must have the same generic type parameters (including constraints) as the non-partial
class declaration. - A
partial
class declaration cannot have anextends
clause, but may have animplements
clause. - A
partial
class declaration cannot have aconstructor
member. - A
partial
class declaration cannot have members with the same name as existing members on a class.- Exception: ambient partial class declaration members can merge with other ambient partial class declaration members if they are compatible overloads, similar to interfaces.
- Non-static property declarations on a
partial
class declaration cannot have initializers. - A
partial
class declaration can have a class decorator. User code that executes in-between the initial class declaration and the partial declaration will be able to observe the class before decorators on thepartial
class are applied.- NOTE: We could choose to disallow class decorators on a
partial
class.
- NOTE: We could choose to disallow class decorators on a
Out of scope
- This proposal does not cover the case where built-in "classes" can often also be called as functions. This case is covered in Suggestion: Merge ambient function and class declarations with the same name. #2959.
- This proposal does not cover the case where authors may have already extended the interface of a built in. This case is covered in Suggestion: Merge ambient class and interface declarations. #2961.
Previous discussions
This has also been discussed previously:
- Suggestion: Extension methods #9 - Suggestion: Extension methods
- Partial classes #563 - Partial classes
- https://typescript.codeplex.com/workitem/100