Typescript: Allow abstract class for local declarations and expressions

Created on 12 Jun 2016  路  6Comments  路  Source: microsoft/TypeScript

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.

Fixed Suggestion good first issue help wanted

Most helpful comment

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...

All 6 comments

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

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".

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.

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...

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鈥檛 feel 100% confident about the type of S; there must be a better way to type it.)

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.

Was this page helpful?
0 / 5 - 0 ratings