Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow class inheritance in ambient contexts, even with a base private constructor #18283

Open
zspitz opened this issue Sep 6, 2017 · 7 comments
Labels
Committed The team has roadmapped this issue Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@zspitz
Copy link
Contributor

zspitz commented Sep 6, 2017

TypeScript Version: nightly (2.5.0-dev.20170902)

Code

declare namespace Foo {
    class Bar {
        private constructor();
        Box: string;
    }
    class Baz extends Bar {
        private constructor();
    }
    function generator(): Baz;
}

let x = Foo.generator();
x.Box = 'abcd';

Expected behavior:
Compile without error.

Actual behavior:
error TS2675: Cannot extend a class 'Foo.Bar'. Class constructor is marked as private.

Use case
This would allow declaring the types of host-supplied objects, with the following features:

  1. can inherit members from each other
  2. cannot be inherited from using standard Typescript/Javascript classes
  3. cannot be assigned to from an object literal with matching members
  4. cannot be constructed, only returned from a factory method
declare namespace com.sun.star.text {
    class XTextTablesSupplier {
        private constructor();
        private typekey: XTextTablesSupplier;
        readonly TextTables: any;
    }
    class GenericTextDocument extends XTextTablesSupplier {
        private constructor();
        private typekey1: GenericTextDocument;
        CharacterCount: number;
    }
}
declare function createInstance(typename: 'com.sun.star.text.GenericTextDocument'): com.sun.star.text.GenericTextDocument;

let x = createInstance('com.sun.star.text.GenericTextDocument'); // OK

x = new com.sun.star.text.GenericTextDocument(); // Error

let y = x.TextTables; // OK

// The following would be an error
x = {
    TextTables: '',
    CharacterCount: 5
};
@aluanhaddad
Copy link
Contributor

It seems more like a protected constructor. If the extensibility of the base class is something you wish to prevent, then I suggest that you opt for true implementation privacy by not exposing it at all. if you want a method to accept a base you can use a union type instead.

@zspitz
Copy link
Contributor Author

zspitz commented Sep 6, 2017

then I suggest that you opt for true implementation privacy by not exposing it at all

@aluanhaddad I don't understand. Do you mean not exposing the base constructor? The compiler would then allow newing up an instance:

declare class Foo {
}
let x = new Foo();

Or do you mean not exposing the base class, only the derived class? But the base class provides members to multiple inheriting classes. It also serves as a type in its own right.

It seems more like a protected constructor.

Not quite. There is such a constructor, but it is unavailable from any Typescript/Javascript code, even from classes which would like to inherit from it.


This error makes perfect sense in a non-ambient contex -- if I define a class as extending the base class which has this private constructor, the compiler must prevent my inheriting class from an actual call to said constructor.

But in an ambient context, a class defined as inheriting from the base class can never actually call the private constructor, so this should be allowed.

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Sep 6, 2017

I did indeed mean not exposing the base class at all, sorry for not being more explicit.

with respect to using the base class as a type, why not expose it as an interface or type alias? I definitely see the argument that this is not unreasonable in an ambient context but it seems a bit odd especially since the constructor function will be very much accessible at runtime to any code trafficking in an instance of any class or any constructor function in the hierarchy.

I'm probably misunderstanding the code that this would enable. Is it related to using instanceof?

@zspitz
Copy link
Contributor Author

zspitz commented Sep 6, 2017

@aluanhaddad The use case here is when the Javascript host supplies objects that look and behave like Javascript objects WRT reading/writing properties and calling methods, but not WRT object construction via new. Presumably instanceof would always return false at runtime, because there is no real prototype chain.

At runtime, there isn't an actual Javascript constructor function; the underlying object is constructed via whatever mechanisms are available to the language used to develop the host (in the case of the LibreOffice API, this is either C++ or Java). private constructor is appropriate here, because the actual constructor is unavailable to any Javascript code.

why not expose it as an interface or type alias?

Because that would allow using a plain Javascript object with the appropriate members when the type is expected. If the API expects a binary C++ created object of type com.sun.star.text.GenericTextDocument, passing in a Javascript object will fail.
In other words, if GenericTextDocument is declared as a class:

declare class GenericTextDocument {
    private typekey: GenericTextDocument;
}

then this can be prevented:

let x: GenericTextDocument = {
    TextTables: '',
    CharacterCount: 5
};

which is not the case if GenericTextDocument is declared as an interface or type alias.

@mhegazy mhegazy added the Suggestion An idea for TypeScript label Sep 6, 2017
@aluanhaddad
Copy link
Contributor

@zspitz I see thank you for taking the time to explain.

@mhegazy mhegazy added the In Discussion Not yet reached consensus label Sep 12, 2017
@RyanCavanaugh RyanCavanaugh added Help Wanted You can do this Committed The team has roadmapped this issue and removed In Discussion Not yet reached consensus labels Oct 10, 2017
@RyanCavanaugh RyanCavanaugh added this to the Community milestone Oct 10, 2017
@zspitz
Copy link
Contributor Author

zspitz commented Oct 15, 2017

@RyanCavanaugh @mhegazy Is there any description of the meaning of the various issue labels, such as "help wanted" and "Committed"?

@RyanCavanaugh RyanCavanaugh modified the milestones: Community, Backlog Mar 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Committed The team has roadmapped this issue Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants