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 abstract class for local declarations and expressions #9110

Closed
ulrichb opened this issue Jun 12, 2016 · 6 comments
Closed

Allow abstract class for local declarations and expressions #9110

ulrichb opened this issue Jun 12, 2016 · 6 comments
Labels
Fixed A PR has been merged for this issue Good First Issue Well scoped, documented and has the green light Help Wanted You can do this Suggestion An idea for TypeScript

Comments

@ulrichb
Copy link

ulrichb commented Jun 12, 2016

TypeScript Version:

1.8.9 / nightly (1.9.0-dev.20160612-1.0)

Code

(() => {
    // works:
    class SomeClass { }

    // emits "error TS1184: Modifiers cannot appear here":
    abstract class SomeAbstractClass { }
})();

Expected behavior:
It should be possible to define abstract classes within functions just like non-abstract ones.

@aluanhaddad
Copy link
Contributor

Yes it should but it doesn't make a lot of sense to do.

@yortus
Copy link
Contributor

yortus commented Jun 13, 2016

It might make sense according to the same rationale that TypeScript supports other local types. It would be helpful to see a motivating example showing the value of a local abstract class.

Note this was sort-of anticipated in the proposal for abstract classes, under the heading "To Be Discussed".

@ulrichb
Copy link
Author

ulrichb commented Jun 13, 2016

I had no real use case, I just wanted to scope the abstract class and its derived types to avoid having global symbols. The workaround was to use a surrounding namespace. It was an artificial sample for a TypeScript talk.

Nevertheless, as local types (for non-abstract classes, interfaces, and enums) are supported, IMO missing support for abstract classes means that this feature is incomplete. Also because the compiler output seems to be correct (without --noEmitOnError) except the mentioned error message.

@mhegazy mhegazy changed the title "Modifiers cannot appear here" for abstract class in function Allow abstract class for local declarations and expressions Jun 13, 2016
@mhegazy mhegazy added Suggestion An idea for TypeScript Help Wanted You can do this Good First Issue Well scoped, documented and has the green light labels Jun 13, 2016
@mhegazy mhegazy assigned ghost Jun 13, 2016
@aluanhaddad
Copy link
Contributor

I agree this should be supported I was just curious what the actual use case was since the base class cannot escape the function anyway, it's only implementations are going to reside within the same scope which should be fairly narrow. However if you're using the function to avoid polluting the global namespace but want to use an abstract class in your design that seems very reasonable. I've been spending too much time writing external modules...

@rauschma
Copy link

This is a use case for abstract class expressions: implementing mixins via subclass factories.

const MyMixin = (S : { new () : Object }) => abstract class extends S {
    abstract foo() {}
    bar() {}
};

// Must implement foo()
class MyClass extends MyMixin(Object) {
}

(I don’t feel 100% confident about the type of S; there must be a better way to type it.)

@ghost
Copy link

ghost commented Aug 11, 2016

Unfortunately, the method you show wouldn't work even if we allowed abstract class expressions.
Here's an example:

class SuperClass {
    superMethod() {}
}

const MyMixin = (S: { new(): Object }) => {
    abstract class A extends S {
        abstract foo(): void;
        bar() {}
    }
    return A;
}

class MyClass extends MyMixin(SuperClass) {
    foo() {}
}
new MyClass().bar();
// ERROR: Property 'superMethod' does not exist on type 'MyClass'.
new MyClass().superMethod();

The problem is that the return type of MyMixin doesn't take into account the specific type passed in by S.
We don't generally support calling a function taking a class and returning a different class and using the result in a statically-typed way.

I'm assuming that the following is what you would want in a perfect world:

class SuperClass {
    superMethod() {}
}

abstract mixin Mixin {
    abstract abstractMethod(): number;
    mixinMethod() {
        return this.abstractMethod() + 1;
    }
}

class MyClass extends SuperClass and also extends Mixin {
    abstractMethod() { return 1; }
}
new MyClass().superMethod();
new MyClass().mixinMethod();

Here's how you can get it done today:

class SuperClass {
    superMethod() {}
}

interface MixinAbstracts {
    abstractMethod(): number;
}

interface Mixin {
    // Implementation provided by factory
    mixinMethod(): number;
}

function mixin<T extends { new(): Object }>(t: T): T {
    return <any> class extends (<{new(): Object}>t) {
        mixinMethod(this: MixinAbstracts) {
            return this.abstractMethod() + 1;
        }
    }
}

interface X extends Mixin {}
class X extends mixin(SuperClass) implements MixinAbstracts {
    abstractMethod() { return 1; }
    ownMethod() {}
}
new X().superMethod();
new X().mixinMethod();

interface X extends Mixin {} declares that your class (somehow) implements the mixin interface.
implements MixinAbstracts is just there to help you to remember to implement abstractMethod.
extends mixin(SuperClass) actually does the work. But this function just has a return type of typeof SuperClass, so you need to use interface X extends Mixin {} to tell the compiler that you've actually added new methods.

@mhegazy mhegazy added this to the TypeScript 2.0.1 milestone Aug 17, 2016
@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Aug 17, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Fixed A PR has been merged for this issue Good First Issue Well scoped, documented and has the green light Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants