Skip to content

Allow interfaces to declare protected members and allow implementors to implement protected and private members. #3854

Closed
@benliddicott

Description

@benliddicott

TL;DR: provide a syntax which kills this compiler error:

Class 'D' incorrectly implements interface 'C'.
Property '_a' is protected but type 'D' is not a class derived from 'C'.

If the implementer wants to take responsibility for making sure the private members behave correctly they should be able to do so.

This would allow mixins to have protected members and solve a few issues with other multiple inheritance use-cases.

Proposal: Allow implementation of protected and private members in derived and implementing classes

Allow interface implementers to implement protected and private methods. This makes it possible for types with private and protected members to be implemented as interfaces (provided the inheritor correctly manages the private state).

Suggested syntax:

    class P {private _p;}
    class Q implements P { private _p implements P; }

Since name mangling is not used we can't alias the private members, so if implementing more than one interface with private members, the private members must either have different names, or the underlying uses of the members must be compatible, or be made compatible.

    class P{private _p;}
    class P2{private _p;}
    class Q implements P, P2{ private _p implements P, P2;}

Of course interfaces can have protected and private members, though they can't declare them directly and you can't implement them directly:

    class P { protected _p = "p"; }
    interface Q extends P { q; };
    // Q can't be implemented except by a class derived from P
    class PX extends P implements Q { q = "q"; }
    class R { protected _r = "r"; }
    interface S extends Q, R { s; }
    // S cannot be implemented by anyone
    // However you could gin up the correct members, and cast to S
    function SFactory(): S { var px = new PX(); px["s"] = "s"; px["_r"] = "r"; return <S>px; }
    // you can even make it constructable:
    class _S extends PX {
        s = "s";
        protected _r = "r";
    }

    var S: new (...args) => S = <new (...args) => S><new()=> any>_S;
    var myS = new S();

I understand the explanation given in #471, that we cannot simply ignore the private members, as e.g. other instances of B need to be able to access B's private members on C. However protected members are part of the API contract too, for inheritors, and we should be able to implement them.

What's more, nothing is truly private in JavaScript. This can be achieved in typescript using the ["_p"] escape hatch and an intermediate class as above.

Motivation

Several uses for this. One is that it seems to be the only way to allow mixins to have private or protected members.

I'm working with a large library, and have come across the following situation, where I need a type which implements two classes/interfaces which derive from a common base.

Given these classes:

    interface A { a; }
    class B implements A {
        protected _a; get a() { return this._a; }
        BFunc() { };
    }
    class C implements A {
        protected _a; get a() { return this._a; }
        CFunc() { };
    }

I need then to create a class implementing both B and C. Note that both B and C have chosen to implement a using a protected backing member _a, and that these are compatible implementations with exactly the same meaning. There is now no way to achieve this.

Allowing interfaces to declare protected members would make them part of the API contract for inheritors and would solve this problem.

This does not work:

    // Class 'D' incorrectly implements interface 'C'.
    // Property '_a' is protected but type 'B' is not a class derived from 'C'.
    class D extends B implements C {
        CFunc() { };
    }

Neither does this:

    // Class 'E' incorrectly implements interface 'B'.
    // Property '_a' is protected but type 'E' is not a class derived from 'B'.
    // Class 'E' incorrectly implements interface 'C'.
    // Property '_a' is protected but type 'E' is not a class derived from 'C'.   
    class E implements B, C {
        _a;
        get a() { return this._a; }
        BFunc() { };
        CFunc() { };
    }

For example if I could reopen the interface like so:

    interface A { protected _a;}

That would solve the issue. By declaring it part of the API contract, albeit one only accessible to inheritors, we solve the problem.

Currently the only way to work around involves making the backing member public in the interface, or public in the derived classes. (There has to be a backing member in this case). Even making the A a class, and declaring the protected member won't allow you to do it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions