Skip to content

Suggestion: Merge ambient class and interface declarations. #2961

Closed
@rbuckton

Description

@rbuckton

Summary

To support re-opening classes and built-ins as classes, I have proposed two suggestions (#2957, #2959) to help to better re-classify built-ins as class declarations, rather than the current interface-interface-var pattern we are using for built-in "classes". However, these changes would break existing projects that currently extend built-ins by re-opening the interfaces of these built-ins. This proposal seeks to support merging these declarations, so that existing code can continue to function.

Current State

Currently, authors can re-open the instance or static-side interface of many built-ins in lib.d.ts to add new functionality. For example:

// lib.d.ts
interface Array<T> { /*... */ }
interface ArrayConstructor { /*...*/ }
declare var Array: ArrayConstructor;

// polyfill.ts
interface Array<T> {
  includes(value: T): boolean;
}
interface ArrayConstructor {
  of<T>(...items: T[]): T[];
}

Array.prototype.includes = function (value: any) { return this.indexOf(value) != -1; }
Array.of = function<T> (...items: T[]) { return items; }

The proposals in #2957 and #2959 would break existing libraries without some remediation method.

Proposal

I propose two steps to resolve this:

  • Allow you to re-open the interface for the instance side of a class.
  • Automatically generate a named interface for the static side of an ambient class declaration in the same lexical scope.

Re-open instance-side

Re-opening the instance-side of a class would be possible by merging interfaces with ambient class declarations in the same lexical scope:

// lib.d.ts
declare class Array<T> { /* ... */ }

// polyfill.ts
interface Array<T> {
  include(value: T): boolean;
}

Automatic static-side interface

For backwards-compatibility, we could auto-generate a constructor interface for an ambient class declaration. As this is really intended to be a backwards-compatibility feature, we could opt to enable this only when we encounter a triple-slash directive. This would allow us to not only use this for our own lib.d.ts, but also allow third parties to use this directive if need to migrate their existing declarations to support these new features.

// lib.d.ts
/// <reference auto-static-interface="true" />
declare class Array<T> { /* ... */ }
// Creates an `ArrayConstructor` symbol for the static interface of Array

// polyfill.ts
interface ArrayConstructor {
  of(...args: T[]): T[];
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixedA PR has been merged for this issueSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions