Typescript: Support final classes (non-subclassable)

Created on 26 Apr 2016  ·  124Comments  ·  Source: microsoft/TypeScript

I was thinking it could be useful to have a way to specify that a class should not be subclassed, so that the compiler would warn the user on compilation if it sees another class extending the original one.

On Java a class marked with final cannot be extended, so with the same keyword on TypeScript it would look like this:

final class foo {
    constructor() {
    }
}

class bar extends foo { // Error: foo is final and cannot be extended
    constructor() {
        super();
    }
}
Suggestion Won't Fix

Most helpful comment

There should also be a final modifier for methods:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

  }
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.

E.g. I often use following pattern, where I want to urgently avoid fooIt to be overridden:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

  public final fooIt():Whatever() {
    //do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
    ImportantVariable = true;
    //call the abstract inner functionality:
    return this.fooIt_inner();    
  }
}

All 124 comments

a class with private constructor is not extendable. consider using this instead.

From what I recalled I was sure the compiler didn't like the private keyword on the constructor. Maybe I'm not using the paste version though

This is a new feature, will be released in TS 2.0, but you can try it using typescript@next. see https://github.com/Microsoft/TypeScript/pull/6885 for more details.

Ok thank you

Doesn't private constructor also make a class not instantiatable out of the class? It's not a right answer to final class.

Java and/or C# uses the final class to optimize your class at runtime, knowing that it is not going to be specialized. this i would argue is the main value for final support. In TypeScript there is nothing we can offer to make your code run any better than it did without final.
Consider using comments to inform your users of the correct use of the class, and/or not exposing the classes you intend to be final, and expose their interfaces instead.

I do not agree with that, instead I agree with duanyao. Private does not solve that issue, because I also want classes which are final to be instanciateable using a constructor. Also not exposing them to the user would force me to write additional factories for them. For me the main value of final support is, that it prevents users from making mistakes.
Arguing like that: What does TypeScript offer to make my code run faster, when I use types in function signatures? Isn't it also only for preventing users from making mistakes? I could write comments describing which types of values a user should pass in as a parameter. It's a pitty, that such extensions like a final keyword are just pushed away, because on my opinion it collides with the original intension of typescript: make JavaScript safer by adding a compilation level, which performs as many checks as possible to avoid as many mistakes upfront as possible. Or did I misunderstand the intention of TypeScript?

There should also be a final modifier for methods:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

  }
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.

E.g. I often use following pattern, where I want to urgently avoid fooIt to be overridden:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

  public final fooIt():Whatever() {
    //do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
    ImportantVariable = true;
    //call the abstract inner functionality:
    return this.fooIt_inner();    
  }
}

The argument about cost vs. utility is a fairly subjective one. The main concern is every new feature, construct, or keyword adds complexity to the language and the compiler/tools implementation. What we try to do in the language design meetings is to understand the trade offs, and only add new features when the added value out weights the complexity introduced.

The issue is not locked to allow members of the community to continue adding feedback. With enough feedback and compelling use cases, issues can be reopened.

Actually final is very simple concept, does not add any complexity to the language and it should be added. At least for methods. It adds value, when a lot of people work on a big project, it is valuable not to allow someone to override methods, that shouldn't be overridden.

In TypeScript there is nothing we can offer to make your code run any better than it did without final.

Wow, cringe! Static types don't make your code run any better either, but safety is a nice thing to have.

Final (sealed) is right up there with override as features I'd like to see to make class customizations a bit safer. I don't care about performance.

Static types don't make your code run any better either, but safety is a nice thing to have.

Exactly. Just as private prvents others from calling the method, final limits others from overriding the method.

Both are part of the class's OO interface with the outside world.

Completely agree with @pauldraper and @mindarelus. Please implement this, this would make a lot of sense I really miss it currently.

I don't think final is only beneficial for performance, it's also beneficial for design but I don't think it makes sense in TypeScript at all. I think this is better solved by tracking the mutability effects of Object.freeze and Object.seal.

@aluanhaddad Can you explain that in more detail? Why do you think it does not "make sense in TypeScript at all"?
Freezing or sealing object means to disallow adding new properties to an object, but does not prevent adding properties to a derived object, so even if I would seal the base class I could still override the method in a child class, which extends that base class. Plus I could not add any properties to the base class at runtime.

The idea of using final on a class or class method in java has more to do with minimizing mutability of the object for thread safety in my opinion. (Item 15. Joshua Bloch, Effective Java)

I don't know if these principals carry over into javascript seeing as everything in JS is mutable (correct me if I'm wrong). But Typescript is not Javascript, yeah?

I would really like to see this implemented. I think it'll help create more robust code. Now... How that translates into JS, it honestly probably doesn't have to. It can just stay on the typescript side of the fence where the rest of our compile-time checking is.

Sure I can live without it, but that's part of what typescript is, right? Double checking our overlooked mistakes?

To me final would play the same role in typescript as private or typings, that is code contract. They can be used to ensure your code contract don't get broken. I would like it so much.

@hk0i its also mentioned in Item 17 (2nd edition) in a manner similar to what's been echoed here:

But what about ordinary concrete classes? Traditionally, they are neither final nor designed and documented for subclassing, but this state of affairs is danger- ous. Each time a change is made in such a class, there is a chance that client classes that extend the class will break. This is not just a theoretical problem. It is not uncommon to receive subclassing-related bug reports after modifying the internals of a nonfinal concrete class that was not designed and documented for inheritance.

The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. There are two ways to prohibit subclassing. The easier of the two is to declare the class final. The alternative is to make all the constructors private or package-private and to add public static factories in place of the constructors.

I would argue it does not increase the cognitive complexity of the language given that the abstract keyword already exists. However, I cannot speak to the implementation / performance impact of it and absolutely respect protecting the language from that angle. I think separating those concerns would be fruitful towards deciding whether or not to implement this feature.

I believe that final would be an excellent addition to seal a class. One use case is that you may have a lot of public methods in your class but expose, through an interface, just a subset of them. You can unit tests the implementation quickly since it has all these public methods while the real consumer uses the interface to limits access to them. Being able to seal the implementation would ensure that no one extends the implementation or change public methods.

You may also ensure that no one is inheriting your class. TypeScript should be there to enforce those rules, and the suggestion about commenting seems to be a lazy approach to solve this use case. The other answer I read is about using private which is only suitable for a particular situation but not the one I explained above.

Like many people in this thread, I would vote to be able to seal class.

@mhegazy Private constructors and sealed/final classes have very different semantics. Sure, I can prevent extension by defining a private constructor, but then I also can't call the constructor from outside the class, which means I then need to define a static function to allow instances to be created.

Personally I'd advocate for having sealed/final compiler checks to ensure that classes and methods marked sealed/final cannot be extended or overridden.

In the context of my problem, I want to be able to have a public constructor so I can instantiate the object, but prevent extension of the class, and only the addition of sealed/final will allow that.

There is a task - write code that

  • operates immutable objects
  • is inheritance free
  • reflection methods free

And final keyword is necessary here, IMHO.

I see final as a great way to remind yourself and users of your code which protected methods they should be overriding, and which they shouldn't. As a sidenote, I'm also a proponent of explicitly stating that you're overriding, partially so that the reader knows, but also so that the transpiler complains if you typo the method's name.

@zigszigsdk Since methods are never overridden, how would this work?

For final:
The same way as it works now, except the transpiler would complain if one hides a super's method -which has been declared final- from the this context.

For override:
The same way it works now, except the transpiler would complain if one declares a method override, and its super doesn't have a method by that name, or it does have one but it's declared to be final.
It could possibly also be warning you if you hide a super's method from the this context and don't state override.

Java and/or C# uses the final class to optimize your class at runtime, knowing that it is not going to be specialized. this i would argue is the main value for final support.

@mhegazy Not quite! Another important function is to be explicit about which parts of the class expect to be overriden.

For example, see item 17 in "Effective Java": "Design and document for inheritance or else prohibit it":

It is not uncommon to receive subclassing-related bug reports after modifying the internals of a nonfinal concrete class that was not designed and documented for inheritance. The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. There are two ways to prohibit subclassing. The easier of the two is to declare the class final.

Same goes for final methods, in my opinion. It's very rare that a class is designed to support overriding any of its public methods. This would require very intricate design and a huge amount of testing, because you'd have to think of every possible combination of states that might result from combination of non-overrided and overriding behaviour. So instead a programmer might declare all public methods final except one or two, which would decrease the number of such combinations dramatically. And more often that not, one or two overridable methods is precisely what's needed.

I think that final classes and methods are very important. I hope you will implement it.

Another compelling user case @mhegazy . Today I learned the Template Method Pattern, and found that final is required to prohibit subclasses changing the template method as the Template Method Pattern in wikipedia says. But I cannot do this in my lovely TypeScript. What a pity!

Template pattern lets subclasses redefine certain steps of an algorithm without changing the algorithm's stucture

For me it's as simple as trying to enforce composition in cases over inheritance. Without final you can't do this.

As much as I'd also like to see final come to typescript, I think the underlying message that Microsoft is trying to send us is that if we want final, we should use a language that supports it.

I would like to see this issue re-opend and implemented.

When building a library that you are going to use internally or share publicly (such as on npm), you don't want those who use it to have to browse the code and search through comments or docs to see if the class can/can't be overwritten. If they overwrite it throw an error.

In my code, I have a class that gets extended, and when some sort of event takes place it triggers a method. If the sub-class defines the method it will that one otherwise it will fall back to the default one. However there are also other methods in the class that are not "fallback" methods, they are more of helper methods such as adding an item to the DOM in which case I don't want these methods to get overwritten.

My 5 cents on the topic are that this should be re-opened and implemented.

I would use it to enforce clients of libraries to make their own concrete classes and not extends from some other existing already. Like @lucasyvas said, favor composition. Or just enforce new concrete class implementation to provide to the Dependency Injection in angular for example.

I also vote for re-opening, with support for both classes and methods

I don't say that we shouldn't I am just unsure of how the pattern of the final keyword would work in the context of typescript?

wouldn't the final keyword stop users/ programmer to override/ inherit from the said class/method?

My way of seing Final is that it's "Freeze" the data which mean it's can't be changed anymore. I mean this can be nice to use it's although can be truly an hassle for peoples who want to "bug" fix or change some behavior.

@niokasgami Freeze is related to [data] (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/freeze)
and already available as part of ES6. Final applies to classes and methods (variable can be readonly and therefore act as "final" as we cannot override a super variable).

The "Final" keyword is used in JAVA where sealed is in C#.

As typescript is strongly inspired by C#, there should be a higher probability that "sealed" will be the chosen one. Now, we could possibly have confusion with the ES6 seal which basically seals the object at runtime?

@Xample I think you can also use final on local variables in Java. I remember having to do it when I was writing an android app. I think it required me to do it with an event but I am not 100% certain.

function(){
    let final myVariable = 'Awesome!'
    document.addEventListener('scroll', e => {
        console.log(myVariable)
        myVariable = 'Not Awesome' // Throws an error
    })
}

@TheColorRed Well, final local variable in Java means constant, isn't it?

@EternalPhane I guess so, I have always used const in a global like scope... Never thought of using it within a function or method... I guess me previous comment is void then...

@Xample Yeah I see your point for Sealed from the C# it's not working as the same Honestly in my opinion BOTH keyword seal and final couldn't really enter the category since both can be confusing (sealed for seal etc) and Final we are unsure how to apply it for since in Java it is used as an constant :/

In java you can final any variable, method or class. For variables it means the references cannot be reassigned (for non-reference types this means you cannot change their values either). For classes it means no extensions (extends... keyword). For methods it means no overriding in subclasses.

Main reasons to final in java:

  • Reduce immutability of variables: for thread safety
  • Finalize classes so they cannot be inherited: forces composition over inheritance because inheritance is inherently complicated when your subclasses start inadvertently calling super methods
  • Prevents simple programming errors: if you have no intent on changing a value, good news it's no longer possible
  • Used for what most people think of when they imagine constants: reusable values that you will see everywhere, as in public static final String KEY = "someStringKeyPathHere", useful for reducing mistakes but has an added compile time benefit of not recreating multiple string objects for the same value

I may have left out a couple of use cases but I was trying to keep it very general and give some examples.

@TheColorRed Android requires it when you declare a variable outside of a callback (anonymous class) and then reference it inside. I think this aligns with thread safety issues again. I think they are trying to stop you from reassigning the variable from another thread.

Despite its usefulness I don't think we will see this feature coming any time soon. It might be a good time to look at other solutions or, as in my case, drop typescript all together and deal with the sloppiness in JS. Javascript has a lot of "benefits" by not having any of this type hinting or anything and ultimately your code will be converted to JS anyway. If you want browser compatibility you can write ES6 and use Babel to convert it.

I think realistically even though you can give a slap on the wrist for some of these benefits, you don't get any of the real benefits of the java-esque final once it's been converted to js.

@hk0i I think I could understand your point but overall I agree and kinda disagree in the same time about your opinion.

Most of the code in typescript are I would say for debugging/tracking purpose for helping the programmer to well I guess do "clean" code (please don't quote me on that I know they have some typescript spaghetti code out here hahah)

most of typescript feature doesn't get transpilled most of the time when converted to JS because they just don't exists in JS.

Although when you runtime/ write your code it's help to see error in your code and to track it more easily than with JS (and HECK JS can be hard to track bugs lol)

So I think the use of the final keyword could be seing as an blocker for API user to know : Oh okay this class shouldn't be extended since it's extension could provoke weird behavior etc...

Typescript as good language it is I find myself sad to find that it lack some important feature such "true" static class keyword who stop peoples from calling the class constructor but still being able to access it.

@hk0i I don't agree neither, the purpose of typescript is to structure the code and therefore providing the tools to do so. We do not HAVE to use final in Java, we typically use it when we know that extending a class or method could lead to introducing side effects (or for any other safety reason).

Relying on the current ES6 solutions adding a runtime mutability check (saying using Object.seal or Object.freeze) makes you loose the benefit of having errors directly when you type your code (despite it could easily be done using this decorator).

@TheColorRed yes… and in typescript we have a distinction for local and class variable.

  • readonly for a class variable
  • const: for any other variable

None of them would be accurate for method or class. Overriding a method does not mean we are redefining it (think in virtual methods in C++), we just define a method will will be primary be called before the super one (which we can call using super).

@niokasgami perhaps the naming is not really a problem as we will still understand that Object.seal applied to… an object while sealing a class and a method to the structure itself.

TL;DR:

  • preventExtensions would be a good explicit name to prevent extending classes
  • preventOverrides would be a good candidate name to prevent overriding classes
    BUT: seal is widely used in C#, shorter and has the same expected behaviour.

By the way I forgot to mention there is also Object.preventExtensions()
Differences can be seen here

reading

  • always possible… who wants a write only variable ?

updating and deleting

  • prevent redefining a deleting a variable: readonly for a class member, const (for any other)
  • prevent redefining a method: nothing prevents you to do (within a class method) this.aMethod = ()=>{}. Should methods be readonly as well to prevent such hacks ? same for delete this.aMethod

creating

  • only applies to object or as we are talking here about structure: class as the class defined the members of the object, typescript implicitely already prevents from creating new properties on the fly… for instance this.unKnownProperty = "something"ts will raise a compilation time error TS2339 as expected.

extending

  • only applies to classes, we are not doing anything on the object itself yet… (unlike the creating which is at runtime). This is where final or seal would be relevant. The question is how to stay consistant wit the freeze, seal and preventExtensions of ES6 ? (if we have to).
    Extending a class, mean we prevent creating another class from this class. We can then read it but not delete it (who deletes class anyway). The question is not the "update" column. Is a final class updatable ? What is a class updatable ? My guess is yes… but then the update would be to implement this class (understanding the interface of the class) ? Implementing another class should always be possible… but it's not related to updating the initial class. Well… perhaps they simply came to the same questions writing c# and decided that seal was the good keyword.

overriding

  • prevent overriding the current method in a extending class. Again, we are not overwriting anything yet as we are talking about a definition (method within a class). So prevent creating, allow reading, allow updating (we could prevent updating the method or deleting it using readonly if it was applicable to methods as well). Best naming ? preventOverrides ? or again seal as it is used in C#

BONUS: As sealing a method is usually to prevent people overriding mandatory super code, I made the proposal of forcing people to call the super method (the same behaviour as for the constructor but for any method) don't hesitate to vote up if you think it would be useful.

Don't get me wrong, I'm not against this. I'm very much for it. I'm just trying to get behind Microsoft's paradoxical mindset of why it shouldn't be a feature, which is essentially saying that because it's not a JavaScript feature it cannot be a TypeScript feature.

... If I'm understanding correctly.

@hk0i Generics, readonly, and many other features are not a JavaScript features, but they are still added into TypeScript, so I don't think that is the reason...

TypeScript only shies away from features which require generating code in a non-straightforward way. Generics, readonly, etc are purely compile-time notions which can be simply erased to produce the generated output, so they’re fair game.

final can be "erased" at compile time too, so why doesn't that make it "fair game"?

🤷‍♂️

@TheColorRed I think this more or less this either they have other priority to integrate.
Or they unsure how predictable this new keyword could work on compile/ coding time. if you think about it the design of final keyword can be extremelly vague. final could be used a lots for many purpose. As you can see from the Jsdoc documentations you can use the tag "@final" which tell the jsdoc interpreter that this variable is frozen thus readonly.

here a design clash. Readonly is aplicable on variable and getter if I recall which "froze" the variable and make it readonly and final make it final thus readonly as well.

this can cause confusion to the programmer who is unsure if they should tag their variables final or readonly.

UNLESS we reserve this keyword exclusively for class and method declarations, I think the behavior or final would clash with readonly.

I think instead of sealed (which can clash with object.seal) or final (which this purpose design clash readonly keyword) we could go with a more "direct" way to name and design it.

take note this merelly a "idea" of a behavior who is similar but in same time different from the C# behavior? I took some inspirations from how C# is normally nonOverridable and then you have to say that this method is virtual.

` namespace Example {

export class myClass {

    constructor(){}

    // Make the func non ovveridable by inheritance. 
    public unoveridable myMethod(){

    }

    public myMethodB(){

    }
}

export class MyClassB extends myClass {

    // Nope not working will throw an error of an nonOverridable error
    myMethod(){
        super.myMethod();
    }

    // Nope will not work since unoverridable.
    myMethod(){

    }

    // Design could be implemented differently since this could complicate  ¸
    // the pattern of typescript
    public forceoverride myMethod(){
      // destroy previous pattern and ignore the parent method thus a true ovveride
    }

    // Yup will work since it's still "virtual".
    myMethodB(){
        super.myMethodB();
    }
}
// Can extend an existing Parent class
// Declaring an unextendable class make all is component / func nonOverridable by 
// inheritance. (A class component can still be overwritten by prototype usage.)
export unextendable class MyFinalClass extends Parent {

    constructor()

    // nonoverridable by default.
    public MyMethod(){

    }
}
// nope throw error that MyFinalClass is locked and cannot be extended
export class MyChildClass extends MyFinalClass{}

}`

Edit : I didn't include Variable and get since readonly already exists.

@mhegazy Please consider reopening this issue. The community response seems to be overwhelmingly on the side of adding a final keyword, like Java (final), C# (sealed, readonly), PHP (final), Scala (final, sealed), and C++ (final, virtual) have. I can't think of a statically typed OOP language that doesn't have this feature, and I haven't heard a convincing argument for why TS doesn't need it.

@bcherny @mhegazy I completely agree with the above. I cannot see any well argued reason why final can't just be another compile-time constraint, like most of the syntactic sugar we see in TypeScript - let's face it, static typing, generics, access modifiers, type aliases, interfaces...ALL SUGAR! I get that the TypeScript team probably want to focus on taking the language in other directions, but seriously, this is OOP 101... this should have been though about a long time ago, when TypeScript was still very young!

@mhegazy may I refer you back to an early comment you made on this issue?

a class with private constructor is not extendable. consider using this instead.

At the time of writing this comment has been down-voted 21 times.
Clearly this is NOT WHAT THE COMMUNITY WANT AS A SOLUTION!

wow, got a lot of feedback about this. I can't understand why no interest to implement

Lots of really unproductive complaining going on here. Please don't just show up to voice frustration - we are allowed to make decisions here and the community is allowed to give their feedback, but just idle gnashing of teeth is not going to change anyone's mind. Please be specific and actionable; we're not going to be convinced by people just showing up to say DO THIS FEATURE NOW.

Note that this issue is about final/sealed classes. There's been off-topic discussion of final/sealed methods which are a completely different concept.

Some points were considered when we reviewed this last time

  • CLASSES ARE A JAVASCRIPT FEATURE, NOT A TYPESCRIPT FEATURE. If you really feel like classes are completely useless without final, that is something to take up with the TC39 committee or bring to es-discuss. If no one is willing or able to convince TC39 that final is a must-have feature, then we can consider adding it to TypeScript.
  • If it's legal to invoke new on something, that has a one-to-one runtime correspondence with inheriting from it. The notion of something that can be new'd but not super'd just doesn't exist in JavaScript
  • We are trying our best to avoid turning TypeScript into an OOP keyword soup. There are many better composition mechanics than inheritance in JavaScript and we don't need to have every single keyword that C# and Java have.
  • You can trivially write a runtime check to detect inheritance from a "should-be-final" class, which you really should do anyway if you're "worried" about someone accidently inheriting from your class since not all your consumers might be using TypeScript.
  • You can write a lint rule to enforce a stylistic convention that certain classes aren't inherited from
  • The scenario that someone would "accidently" inherit from your should-be-sealed class is sort of an odd one. I'd also argue that someone's individual assessment of whether or not it's "possible" to inherit from a given class is probably rather suspect.
  • There are basically three reasons to seal a class: Security, performance, and intent. Two of those don't make sense in TypeScript, so we have to consider the complexity vs gain of something that's only doing 1/3rd the job it does in other runtimes.

You are free to disagree with all of these, but please bring some actionable and specific feedback about how the lack of final is harming your ability to effectively use JavaScript classes.

haven't heard a convincing argument for why TS doesn't need it

Just a reminder that this is not how it works. All features start at -100. From that post

In some of those questions, the questions asks why we either “took out” or “left out” a specific feature. That wording implies that we started with an existing language (C++ and Java are the popular choices here), and then started removing features until we got to a point where we liked. And, though it may be hard for some to believe, that's not how the language got designed.

We have a finite complexity budget. Everything we do makes every next thing we do 0.1% slower. When you request a feature, you're requesting that every other feature become a little bit harder, a little bit slower, a little bit less possible. Nothing gets in the door here because Java has it or because C# has it or because it'd only be a few lines of code or you can add a commandline switch. Features need to pay for themselves and for their entire burden on the future.

@RyanCavanaugh While I'm not sure I agree with the decision, I do appreciate the really thoughtful and detailed response, and for your taking the time to write it. I get that you want to keep the language lean - it's good to reflect on whether features that other popular languages have are really valuable, or just common.

@RyanCavanaugh

I don't think the point here, in this forum is .. "effectively use JavaScript classes."

Sorry, I can not resist, and just add to the "unproductive complaining going on here": the final keyword is usefull for all the reasons explained, in detail, and with arguments by others.

We are trying our best to avoid turning TypeScript into an OOP keyword soup. There are many better composition mechanics than inheritance in JavaScript...

Like when you use final to enforce composition over inheritance in (insert not typescript language name here) 😈
(Sorry, I couldn't resist)

But more to the point, it seems like TS intends to mostly be a compatibility layer to introduce "today's JavaScript now" or something to that effect (a la Babel) with the exception that it adds some additional type checking.

I think the gist of the counter argument for implementing this is that if you want another language's features, use the other language instead. If you want JavaScript, use JavaScript. If you want TypeScript, use that.

I know Kotlin can compile to JS and has a lot of the features that I think the people here would enjoy so that is something that might be useful to look into. I think it does add some JS library as a dependency though (I haven't tried it myself).

The main takeaway being that if you're not happy with TypeScript, don't use it.

@RyanCavanaugh

On the points you raised above...

CLASSES ARE A JAVASCRIPT FEATURE, NOT A TYPESCRIPT FEATURE. If you really feel like classes are completely useless without final, that is something to take up with the TC39 committee or bring to es-discuss. If no one is willing or able to convince TC39 that final is a must-have feature, then we can consider adding it to TypeScript.

I've been following TypeScript since 0.7 and back then, IIRC it was targeting ES3-ES5. Classes were definitely a TypeScript feature back then. The same argument applies to decorators - they're on the cards for ES, but they're already here in TypeScript as an experimental feature.

As a community, we're not arguing that the TypeScript compiler must somehow emit a JavaScript class that's final . A compile-time check would suffice, in the same way that compile time checks for access modifiers suffice; after all, they're not JavaScript features either, but TypeScript still makes them possible.

If final did become an ES feature, then presumably you could refactor that bit of the emitter when targeting ES* to output final appropriately (In the same what you did for classes, when they were introduced in ES6)?

If it's legal to invoke new on something, that has a one-to-one runtime correspondence with inheriting from it. The notion of something that can be new'd but not super'd just doesn't exist in JavaScript

Again, I don't think the community are expecting you to magic something up here to enforce final at runtime. As you've accurately stated, it "doesn't exist in JavaScript", but as mentioned, a compile-time constraint would suffice.

We are trying our best to avoid turning TypeScript into an OOP keyword soup. There are many better composition mechanics than inheritance in JavaScript and we don't need to have every single keyword that C# and Java have.

It's a fairly well known phrase in the software development community as a whole: "Favour composition over inheritance", however "favour" doesn't mean don't use inheritance as a blanket statement. Sure, you don't _need_ every single keyword that C# and Java have, but you must have had a rationale for allowing abstract classes (which by the way "doesn't exist in JavaScript"), so why not final classes?

You can trivially write a runtime check to detect inheritance from a "should-be-final" class, which you really should do anyway if you're "worried" about someone accidently inheriting from your class since not all your consumers might be using TypeScript.

The same could be said for C# and Java, but I don't need runtime checks because I have sealed and final to do that for me at the compiler level.

If not all my consumers were using TypeScript, I'd have a lot more to worry about than just inheriting from a final class; for example, access to private or protected members, no static type checking, or the ability to construct an abstract class.

Should our code really be littered with runtime checks for all these things (some of which aren't even possible I might add) just to make sure our non-TypeScript consumers are as safe as possible?

You can write a lint rule to enforce a stylistic convention that certain classes aren't inherited from

This would imply that whoever is consuming your code is running the linter, so it's still not really a proper solution; it's a workaround.

The scenario that someone would "accidently" inherit from your should-be-sealed class is sort of an odd one. I'd also argue that someone's individual assessment of whether or not it's "possible" to inherit from a given class is probably rather suspect.

Good/Great developers think about their rationale for doing something, like extending a class. Any less than that and you end up with a "but it works" mentality, without really understanding why.

Asking a question that starts with can I usually tends to have a more definitive answer than questions that start with should I.

There are basically three reasons to seal a class: Security, performance, and intent. Two of those don't make sense in TypeScript, so we have to consider the complexity vs gain of something that's only doing 1/3rd the job it does in other runtimes.

Arguably I'd say that TypeScript would actually fulfil two of these reasons: Security and intent, however if this was implemented purely as a compile time check, then it could only do this at the compiler level, rather than, as you rightly point out, at the runtime level.

I don't think that not having final is harming my ability to effectively use JavaScript classes, nor are JavaScript classes unusable without it. I feel that final is more on the side of a "nice to have" feature, rather than a "necessary" feature, but then the same could be said for many of TypeScript's features.

I think the main gripe here is that TypeScript allows us to create abstract and _open_ classes, but in terms of inheritance and polymorphism it doesn't allow us to paint a complete picture in an elegant and expressive way.

Hiding the constructor isn't what the community want because this takes semantic and expressive value away from class implementations, and once again, because access modifiers are a "nice to have", this can't even be enforced at runtime; essentially a "doesn't exist in JavaScript" situation.

As a developer I have many hats; at the moment I have a C# hat, a Kotlin hat and a TypeScript hat.

  • With my Kotlin hat on, I don't have to worry about final because classes are final by default.
  • With my C# hat on, I apply sealed as a matter of habit, forcing my consumers to ask can I rather than asking should I. More often than not, C# classes should actually be sealed and it's often overlooked.
  • With my TypeScript hat on, I have to hide the constructor as a workaround, because in my (and the communities) opinion, the language lacks something useful.

Out of interest, when the TypeScript team sat around a table and discussed abstract classes, did anyone raise their hand and say "but what about final"?

@RyanCavanaugh Hum I do understand your points

I think mostly it's was an misunderstanding (I guess from how we explained)

@series0ne summary really well that our intentions wasn't too complicate the typescript output by asking to create final class in JS (heck this would be an train wreck to do so) but to just create extra rules for tell peoples : Nope you can't do that. and it's tell the user of the API that they probably shouldn't mess with those class (for stability, security reason etc) ALL on the typescript files/compiler NOT on the output.

Do I think it's would be nice to have this feature : yes it's nice to have more fast way to do some technique.

Is it TRULY needed? Not really this could be something considerate minor and shouldn't be a priority if the Typescript team can afford to implement this extra then it could be nice to have.

I do see that the final/sealed keyword could complicate a LOTS some aspect especially : Bug fix, Patch, change of behavior.

if I explain this, I could totally see an abusive use of final by locking ALL the class from being extended. Provoking IMO a big pain in the a** to deal with. I don't want to use an API where I am locked to do any change to the user class by inheritance because they was to paranoid because of "security".

This would mean I would have to go using prototype then alias or override this class just for the sake to be able to change this class behavior for fit my needs.
this would be annoying especially if I just want to add extra functionality to the said class and don't want to go with a destructive workflow.

After this just my though I might misunderstand myself the situations right now so be free to explain it to me :)

Ah if I recall I was reading a lots about the ES4 (I am unsure I was still a kid when the draft was in productions lol) and surprisely it's look really similar to Typescript.

and it's say in the draft specifications here :
https://www.ecma-international.org/activities/Languages/Language%20overview.pdf

A class that does not explicitly extend any other class is said to extend the class Object. As a consequence, all the classes that exist form an inheritance hierarchy that is a tree, the root of which is Object. A class designated as final cannot be extended, and final methods cannot be overridden: class C { final function f(n) { return n*2 } } final class D extends C { function g() { return 37 } }

so Technically the final keyword was planned into the Ecmascript 4th edition. Do we will see it in the futur integrations of JS I doubt about it but they indeed integrated class so this might be possible but who know?

I have one use case that I just barely ran into that drove me to find this issue. It revolves around the use of this as a return type:

abstract class TileEntity {
    //constructor, other methods...
    abstract cloneAt(x: number, y: number): this;
}

My intention in writing this was to ensure that TileEntities are immutable (and therefore safe to pass around) but that any given TileEntity could be cloned with a few changed properties (in this case x and y) without having to know anything about the shape of the subclass. That is what I want to write, but attempting to write an implementation (correctly) causes this to break.

class TurretTileEntity extends TileEntity {
    //constructor, other methods...
    cloneAt(x: number, y: number): this {
        return new TurretTileEntity(x, y, /* ... other properties */);
    }
}

The problem is that without being able to declare that there will _never_ be a subclass of TurretTileEntity, the this return type cannot be narrowed down to just the type TurretTileEntity.

The impact of this shortcoming in Typescript is minimal, considering you can cast the return value to <any>. This is, objectively, an antipattern, and a useful indication that the type system could be more expressive. Final/sealed classes should be supported.

Out of interest, when the TypeScript team sat around a table and discussed abstract classes, did anyone raise their hand and say "but what about final"?

Yes. Every time we add a keyword, "but then we'll have to add this other keyword too!" is a strong point against adding it, because we want to grow the language as carefully as possible. final on class declarations is in this camp as well - if we have final classes, then we'll "need" final methods or properties as well. Anything that looks like scope creep is regarded with extreme suspicion.

I think that final classes and methods are very important. I hope you will implement it.

Note that this issue is about final/sealed classes. There's been off-topic discussion of final/sealed methods which are a completely different concept.

mhegazy closed https://github.com/Microsoft/TypeScript/issues/9264 in favor of this issue. Is there a separate issue that is not closed as a dupe to track final/sealed methods?

@crcla From reading some of @RyanCavanaugh's comments above, I believe the rationale for closing the other issue is because supporting final/sealed classes encompasses supporting final/sealed methods. The two are quite closely related topics.

I can not believe the propose for final is labeled as Won't fix.

Change the label back to In Discussion and implement it, please!

In my case, I want to write an abstract base class for the plugins and I want to make sure that the plugin class can not overwrite my base class methods by mistake. None of the private constructor, Object.seal can do this job.

To those who see this as a crucial, live-or-die feature: it's really not. If your API consists of exposing a class implementation that needs to be extended by the consumer, there are better ways to do this. Don't follow React's bad example. Just use functions and simple objects, and leave this OO nonsense where it belongs: in the past.

Now bring on the downvotes 😀

Could someone explain me how this discussion does not distinct between final classes and final methods? Of course preventing some methods from overriding adds enormous value. I want to provide the possibility for a plugin developer to add functionality by subclassing some of my frameworks classes and implementing abstract functions - but at the same time make sure she does not (why ever!) override the methods that call these abstract functions (and make sure the error handling/loggin/checking takes place.

@pelotom If you don't want to use OOP constructs and patterns, just don't, no one's telling you to do otherwise, however the same should apply in the opposite direction: if someone wants to use OOP constructs, just let them.
By forcing others to adopt a specific (non-OOP) pattern, you're adopting the same behaviour as the one you denounce OOP for. At least be consistent :)

Adding a final keyword to TypeScript is not a breaking change and won't influence any of your existing projects in any way, however it will be a vital feature for people such as @thorek, myself, and many others.

if someone wants to use OOP constructs, just let them.

We’re not debating whether people should use OOP constructs, we’re debating whether currently non-existent OOP constructs should be added to the language. Doing so takes developer time that could be spent on other features, and makes the language more complex, which slows the rate of future features (because for every new feature it must be considered how it will interact with every old feature). So it certainly does affect me, because I’d rather spend TypeScript’s development and complexity budget on more useful things!

fwiw, we're probably going to encourage our customers (google) to use @final in comments so it propagates to closure compiler :\

Adding _final_ on classes and methods is useful when an R&D team builds TS libraries that other teams can use. These keywords are (an important) part of service exposure stability of such tools.
The keyword is more important for large teams and less important for small projects, imho.

@dimiVergos This is exactly my case, and I assume the same for many others. I can imagine I may seem biased due to my position, but it seems the only (albeit highly) justifiable use of this keyword according to my experience.

I'm open to corrections on this one!

I care more about supporting final methods (to avoid subclasses accidentally overriding them) than I do final classes. Is the the right ticket to vote for that? Ticket https://github.com/Microsoft/TypeScript/issues/9264 seems to imply so.

So how do I vote for "final methods"? Or did I just vote for it by this comment? Also how do we add "methods" to the title? Can I just edit it? The current title is misleadingly limited solely to classes but the discussion includes methods too.

Like many others I would like to see final methods in TypeScript. I've used the following approach as a partial workaround that is not immune to failure but at least helps a little:

class parent {
  public get finalMethod(): () => void {
    return () => {
      // final logic
    };
  }
}

class child extends parent {
  // ERROR "Class 'parent' defines instance member accessor 'finalMethod', but extended class 'child' defines it as instance member function"
  public finalMethod(): void { }
}

Here's another real world use case for final methods. Consider the following extension of a React.Component to make it easier to implement a simple Context.

interface FooStore{
  foo: string;
}

const {Provider, Consumer} = React.createContext<FooStore>({
  foo: 'bar';
});

abstract class FooConsumerComponent<P, S> extends React.Component<P, S> {

  // implement this instead of render
  public abstract renderWithStore(store: FooStore): JSX.Element;

  public final render() {
    return (
      <Consumer>
        {
          (store) => {
            return this.renderWithStore(store);
          }
        }
      </Consumer>
    );
  }
}

Without final on the render() method, nothing stops one from incorrectly overriding the method, thus breaking everything.

Hi, like many others I would like "final" or "sealed" keyword at least for methods. I totally agree that this keyword is a really added value as it allow developper to prevent their librairies customer to extend crucial code blocks (like in plugin for example).

The only workaround I found is to use method decorator like :
function sealed(target, key,descriptor){ descriptor.writable = false; // Prevent method to be overriden }

then in your "base" class, use decorator to prevent method override, example :

class MyBaseClass {
@sealed
public MySealedMethod(){}

public OtherMethod(){}

...
}

This way 'MySealedMethod' couldn't (easily) be overriden in sub class. Exemple:

class AnotherClass extends MyBaseClass {
public MySealedMethod(){} ====>> ERROR at RUNTIME (not at compile time)
}

actually, the only downside of this workaround is that the "sealed" is only visible at runtime (error in javascript console) but NOT at compile time (as the method properties are set via function I guess)

@RyanCavanaugh

Yes. Every time we add a keyword, "but then we'll have to add this other keyword too!" is a strong point against adding it, because we want to grow the language as carefully as possible.

It seems to me that you're starting this argument with your mind set up against the feature. A popular feature should not be a point against considering such feature. It should be a point towards asking yourself _why it's popular_, and then carefully consider it.

I'm sure you've discussed it and arguments have been flung around towards considering it or against it. Is the argument against it just that it's feature creep, or is there anything more specific? Do you consider it a fringe feature? If so, could we do something to change your mind?

Let me just leave these questions here for all of us to ponder.

Is there a way to seal a class _by design_ using present constructs?
If the feature was added, would it allow us to do anything new? (As in, something present constructs don't permit)
If the feature did allow us to do something new, is it something valuable?
Would adding sealed classes increase language complexity too much?
Would adding sealed classes increase compiler complexity too much?

For the purpose of discussing these, I'm not including sealed methods in the discussion and I'm also not including run-time checks in the implementation. We all understand we can't enforce this on Javascript.

FYI: I'd be awesome if the counter to this feature was a bit more than just blanket statements against feature-creep.

@asimonf I think everything you've said was already addressed by this comment (you will need to expand the "show more" section to see it) and the one following it.

What would change our mind here? People showing up with examples of code that didn't work the way it should have because the sealed keyword was missing.

The scenario here that the keyword attempts to address is one where someone mistakenly inherits from a class. How do you mistakenly inherit from a class? It's not an easy mistake to make. JavaScript classes are inherently fragile enough that any subclass must understand its base class's behavioral requirements, which does mean writing some form of documentation, and the first line of that documentation can simply be "Do not subclass this class". Or there can be a runtime check. Or the class constructor can be hidden behind a factory method. Or any other number of options.

Why must TypeScript have this keyword when the situation is already one in which you should have had encountered at least two separate roadblocks telling you not to be there to begin with?

I've been programming for approximately forever years now and I have not once tried to subclass something only to find out it was sealed and I shouldn't have tried. It's not because I'm a super genius! It's just an extremely uncommon mistake to make, and in JavaScript these situations are already ones where you need to be asking questions of the base class in the first place. So what problem is being solved?

A real case I would be using final is to prevent overriding a method with something which would never call the super method. Some public method of course. But the real solution I figured out would be the ability to force any sub method to call the super one. https://github.com/Microsoft/TypeScript/issues/21388

As I'm not a language designer, I can't speak to the drawbacks of adding a keyword to the language. I will say that I am currently working on an abstract superclass that has a method I intend to be final, but to my dismay found that I couldn't make it so. For now I will go with the less-than-ideal approach of putting a comment, but team members implementing the class might not read said comment.

I think that above there are plenty of excellent examples of when/where final would be not only useful, but provide _real value_ and _real safety_ to the platform being built. It's no different for me. As I said, I don't know much about language design complexities but this is clearly a feature that people want/will use/I can't really see a way to abuse it or that it would generate bad code. I understand the concept of fearing scope creep but TS developers don't have to implement everything the community wants.

Just to add my bit to the debate, I would like to put my thumb on the yes-side of the scale because this issue is replete with good arguments supporting the addition of the feature, yet the arguments against that I see are:

  • It could lead to scope creep (this is manageable)
  • People will ask for other features (okay, but who cares, that's part of designing a language and this isn't legal precedent we're setting)
  • It could reduce efficiency (I imagine that since the TS team is full of bad-asses you'd probably find a way to make this manageable)
  • Opinionated "Don't use OOP concepts" comments

None of the above seem like credible threats to the TS ecosystem itself, nor the code that is produced out of it; they all seem political (except maybe the efficiency one, but I have huge confidence in you guys so I'm not really worried about it, plus a minor efficiency decrease would be worth not blowing up important component/library constructs by unwitting inheritance mistakes).

Hope this is constructive! Love TS and want to see it keep improving!

👍

--edit--

In response to @RyanCavanaugh:

So what problem is being solved?

The problem being solved I think is outlined by many, many comments above. I don't mean this as an insult but that question does make it sound, to me, like you aren't actually paying attention to the arguments presented, and that you have your mind set on "no". I don't mean disrespect with that, its just that, honestly, this thread is full of examples of problems that this would solve.

Somehow I missed this comment, which very well outlines a serious concern for adding the feature and that makes sense to me. Again, I don't know the complexity of adding it but efficiency concerns aside (since it's not up to developers to worry about TS efficiency, that's the job of the TS team), there don't seem to be any good arguments against final (in my humblest of opinions).

Most of the recent discussion has been centered on final methods (less so on final classes). Final methods are certainly my primary interest. I just noticed that if you expand the "this comment" link in @RyanCavanaugh 's previous post that he considers the final method discussion off-topic. However @mhegazy suggested that this ticket is the right place (when closing #9264). So as a lowly end-user I'm baffled as to where I should be asking for/voting for final methods. Guidance would be appreciated.

@cbutterfield
The contradiction is something that I noticed as well but did not point out in my post for fear of ranting (I actually found THIS thread through #9264 and came here at @mhegazy's direction).
@RyanCavanaugh
@mhegazy
Per the comment above, where _is_ the correct place to discuss final methods, because that actually is the thing I'm most interested in.

Looking back on my heavily downvoted comment (make sure to go add your 👎 if you haven't yet!) I'm happy to report that React is _no longer_ setting a bad example by forcing a class-based API on its users! With the Hooks proposal, the React team has come to their senses and embraced a radically simpler functional approach. With that, the last reason for many developers to have to use classes at all has evaporated, and good riddance. I'll reiterate: everything you think you need classes for can be done better without them.

The future of JS and TS is classless, praise be!

@cbutterfield I noticed that too, and if issue #9264 was closed to be discussed in this issue, I think the title should change to Suggestion: Final keyword for classes and methods, otherwise people looking for final methods (exclusively) might not add a reaction in the first post.

Quoting @mhegazy:
Java and/or C# uses the final class to optimize your class at runtime, knowing that it is not going to be specialized. this i would argue is the main value for final support. In TypeScript there is nothing we can offer to make your code run any better than it did without final.

I fundamentally disagree. I've developed in Java for many years in many teams and we have never used final classes or methods for runtime optimizations; in practice it is primarily a OOP design idiom. Sometimes I just don't want my class or method to be extended/overridden by external subclasses be it to protect and guarantee a function, or to limit a library's API noise to the essentials, or to protect API users from misunderstanding a complex API.

Yes those issues can be solved by adding comments or interfaces, but final is an elegant solution to reducing visible API to the necessary functions when all you want is a simple class with a clean extendable API.

Even though this issue is nearly 3 years old, we really should have a final keyword as there's already an abstract keyword.

Keep in mind that we do not want to force people to use it and it's a feature as useful as the abstract keyword. But here's another usecase that will strongly show the benefit of a final keyword:

abstract class A {
  protected abstract myVar: string;

  protected abstract myFunction(): void;
}

class B extends A {
  protected readonly myVar: string = "toto";

  constructor() {
    super();
    this.myFunction();
  }

  protected myFunction() {
    console.log(this.myVar);
  }
}

class C extends B {
  constructor() {
    super();
  }

  protected myFunction() {
    console.log("tata");
  };

  public callFunction = () => {
    this.myFunction();
  }
}

const myB = new B(); // toto

const myC = new C(); // toto
myC.callFunction(); // tata

Result after compilation :

toto
tata

So in this code, We have an abstract class A that have abstract methods and properties, we want the inherited class to define them, but we would like to not let any other classes to override those implementations.
Best we could do would be to keep the protected keyword, but as you see, we can still redefine the attributes.

So what if we go further in the Typescript compilation process and use the readonly to protect our attributes (and pretend our methods are properties) ?

class B extends A {
  [...]

  protected readonly myFunction = () => {
    console.log(this.myVar);
  }
}

class C extends B {
  protected myVar: string = "I am not protected that much";
  [...]

  protected myFunction = () => { // using the readonly keyword doesn't prevent any modification
    console.log("tata"); // If you launch this code, tata will still be logged in the console.
  };
}

(Note that the code is compiled with tsc, no errors thrown when compiling or executing it)
So now we have two problems :

  • We don't protect our B attributes, so we need a way to prevent any unexpected inheritance.
  • Now we know that we can override any readonly properties by extending its class, and YES, Typescript knows that it's the parent property we're changing as using private instead of protected in C class will throw an error as it's not the same access type/visibility.

At the moment, we could use decorators (sealed like the official Typescript documentation shows, or a final decorator ) but remember that it is only useful in runtime, and that we must prevent this in the compilation process instead.

I'll look if there's an issue about the "security breach" (if we can call that) when we want to override a protected readonly value and we actually can and we shouldn't. Otherwise I'll open an issue to debate on the verification of the readonly keyword among private, protected keyword with inheritance stuff.

tl ; dr : The final keyword would solve two problems (though the readonly verification would be a good start to prevent any unauthorized rewrite)

Three years later... it's so ridiculous.

In order to prove the need for this feature, I suggest to take a look what was done in other languages:

Java:
final class SomeClass { ... }
PHP:
final class SomeClass { ... }
C#:
sealed class SomeClass {...}
C++:
class SomeClass final { ... }
Delphi:
type SomeClass = class sealed() ... end;

So, we see that in all the most popular OOP languages this feature exists because it comes from the inheritance logic of OOP.

Also I have to quote the Dev lead of TS saying

if we have final classes, then we'll "need" final methods or properties as well.

I'm not sure if it's ironic, but in JS the readonly keyword is used for properties (and methods if you cheat), so that's a pretty dumb point.

And maybe not letting one guy closing the issue when he have +40 downvotes meaning the community disagree strongly with him would be a good advice to avoid further dumb situations.

If you're not interested in reinforcing the main feature of Typescript, please say it loud so that tech news can relay that on Twitter, because otherwise, it's a just an hidden bad buzz. You're just ignoring your community, AND you have no process in making the community validating what we need or not.

maybe not letting one guy closing the issue when he have +40 downvotes meaning the community disagree strongly with him would be a good advice to avoid further dumb situations

I'm not "one guy". When I make decisions here, it is the reflection of the entire TypeScript team's decision-making process. The design team has looked at this issue multiple times, and each time come to the same conclusion. You are welcome to disagree with that outcome, but the process is not up for debate.

you have no process in making the community validating what we need or not.

This is the process, right here: You calling my summarization of our reasoning "dumb" and me (and other people on the team) reading it. We're listening to the feedback.

I know pointing to other implemented features as reasons for considering this one is a smell and not really an argument, but I find it ironic that a type aliasing mechanism was considered and added (it may well be awesome, but one could certainly live without it) but something like this is rejected.

In the end, I can live without such a feature, but the same could be said for about half the features TS has. So, then, what would be an appropriate reason to consider implementing this?

GitHub unhelpfully hides a lot of comments here, so I'm reposting various responses (plus some additions) we've given in the past. Adding some thoughts in below as well.


Some points were considered when we reviewed this last time

  • CLASSES ARE A JAVASCRIPT FEATURE, NOT A TYPESCRIPT FEATURE. If you really feel like classes are completely useless without final, that is something to take up with the TC39 committee or bring to es-discuss. If no one is willing or able to convince TC39 that final is a must-have feature, then we can consider adding it to TypeScript.
  • If it's legal to invoke new on something, that has a one-to-one runtime correspondence with inheriting from it. The notion of something that can be new'd but not super'd just doesn't exist in JavaScript
  • We are trying our best to avoid turning TypeScript into an OOP keyword soup. There are many better composition mechanics than inheritance in JavaScript and we don't need to have every single keyword that C# and Java have.
  • You can trivially write a runtime check to detect inheritance from a "should-be-final" class, which you really should do anyway if you're "worried" about someone accidently inheriting from your class since not all your consumers might be using TypeScript.
  • You can write a lint rule to enforce a stylistic convention that certain classes aren't inherited from
  • The scenario that someone would "accidently" inherit from your should-be-sealed class is sort of an odd one. I'd also argue that someone's individual assessment of whether or not it's "possible" to inherit from a given class is probably rather suspect.
  • There are basically three reasons to seal a class: Security, performance, and intent. Two of those don't make sense in TypeScript, so we have to consider the complexity vs gain of something that's only doing 1/3rd the job it does in other runtimes.

haven't heard a convincing argument for why TS doesn't need it

Just a reminder that this is not how it works. All features start at -100. From that post

In some of those questions, the questions asks why we either “took out” or “left out” a specific feature. That wording implies that we started with an existing language (C++ and Java are the popular choices here), and then started removing features until we got to a point where we liked. And, though it may be hard for some to believe, that's not how the language got designed.

We have a finite complexity budget. Everything we do makes every next thing we do 0.1% slower. When you request a feature, you're requesting that every other feature become a little bit harder, a little bit slower, a little bit less possible. Nothing gets in the door here because Java has it or because C# has it or because it'd only be a few lines of code or you can add a commandline switch. Features need to pay for themselves and for their entire burden on the future.


What would change our mind here? People showing up with examples of code that didn't work the way it should have because the final keyword was missing.

The scenario here that the keyword attempts to address is one where someone mistakenly inherits from a class. How do you mistakenly inherit from a class? It's not an easy mistake to make. JavaScript classes are inherently fragile enough that any subclass must understand its base class's behavioral requirements, which does mean writing some form of documentation, and the first line of that documentation can simply be "Do not subclass this class". Or there can be a runtime check. Or the class constructor can be hidden behind a factory method. Or any other number of options.

In other words: the only correct process for inheriting from a class in JavaScript always starts with reading that class's documentation. There are too many constraints the base class might have to simply derive and start coding. If, at the very first step, you should already know not to try, what value is the static enforcement providing at step three?

Why must TypeScript have this keyword when the situation is already one in which you should have had encountered at least two separate roadblocks telling you not to be there to begin with?

I've been programming for approximately forever years now and I have not once tried to subclass something only to find out it was sealed and I shouldn't have tried. It's not because I'm a super genius! It's just an extremely uncommon mistake to make, and in JavaScript these situations are already ones where you need to be asking questions of the base class in the first place. So what problem is being solved?

If you're not interested in reinforcing the main feature of Typescript, please say it loud so that tech news can relay that on Twitter

We're very explicit about how we design the language; non-goal number one speaks exactly to this suggestion.


My personal reaction after reading the many comments here is that there's a strong biasing effect due to constructs that exist in other languages. If you just approach this from a blank slate perspective, there are dozens of behavioral you could imagine, of which TypeScript has only a small subset.

You could easily imagine some keyword that was applied to a method and enforced that derived methods called it via super. If C# and Java had this keyword, people would absolutely apply that keyword in places where it made sense. In fact, it'd arguably be much more commonly enforced than final, because it's impossible to apply a runtime check for, and is a much more subtle aspect of the base-derived contract than "derived classes may not exist". It'd be useful in a variety of ways that final wouldn't be. I would much rather have that keyword than this one (though I think neither meets the complexity vs value bar).

So, then, what would be an appropriate reason to consider implementing this?

When we look at feedback, strong considerations look like this:

  • I can't adequately express the behavior of this JavaScript library
  • My code compiled, but really shouldn't have because it had this (subtle) error
  • The intent of this code is not adequately communicated by the type system

final hits these, but so would a modifier that says "This function can't be called twice in a row" or "This class's construction doesn't really finish until the next async tick". Not everything imaginable should exist.

We've never seen feedback like "I spent hours debugging because I was trying to inherit from a class that wasn't actually subclassable" - someone saying that would rightly trigger a 'wat' because it's not really imaginable. Even if the modifier existed, it wouldn't really help the situation, because libraries don't document if classes are intended to be final - e.g. is fs.FSwatcher final ? It seems like even the node authors don't know. So final is sufficient if the authors know that it's final, but that's going to be documented regardless, and a lack of final doesn't really tell you anything because it's often simply not known either way.

I read the entire block and understand the statement of the team on the choice of features, I'll just go back on certain points from my comments

I'm not "one guy". When I make decisions here, it is the reflection of the entire TypeScript team's decision-making process.

Sorry I was refering to mhegazy and the pretty massive feedback he got from the community that uses Typescript.

If you really feel like classes are completely useless without final, that is something to take up with the TC39 committee or bring to es-discuss. If no one is willing or able to convince TC39 that final is a must-have feature, then we can consider adding it to TypeScript.

I do not think final is the first step as there's already a proposal for the private keyword https://github.com/tc39/proposal-private-methods

I'm skeptical about the You should a comment to say *dont do this*, it's like saying on a form Hey don't write down specific characters, you coded for nearly forever years so you should know the n°1 rule of a dev: Never trust the user (and the dev that will use your code)

I'm not saying we must absolutely stop everything and put a billion dollars to implement the final keyword, because the usecases are too low to be that efficient, also consider that I gave few exemples where the limit are both from TS and JS and that if people choose TS, it's to prevent the errors in compile-time, not in run-time. If we want things to blow up at runtime, we can use JS and stop caring about TS, but that's not the point, because there's an ultimate usecase that shows how I use the final keyword : I want to lock a method, I don't want anyone to override it.

And as Javascript is limited by that, the community thought Typescript could go beyond that limit, that's why this issue has been on for 3 years, that's why people are still wondering why this feature is not here, and that's why we want the compiler to do the manual checking of a class and/or method.

You didn't wait JS to have private/public methods to implement them, though there are actually proposals to include them with the # keyword (less verbose than public/private), I know you wanted to create a perfect language because perfection is not when you can't add anything anymore, it's when you can't remove anything anymore.

If you can find a solution to prevent a method to be overwritten in the compile process (and not in runtime-process as the point wouldn't be valid), be my guest.

I tried to show the weakness of the situation (on methods/properties and not class), because any TS dev can rewrite any libraries they want, breaking anything they want, maybe that's the beauty of the thing, I guess.

Thanks for the reply by the way, no hate or bad behavior intended against the dev team or you.

All valid points! But, please let’s split the discussion between

a) Support final classes
b) Support final methods

While all your arguments target a) - none targets b.

Having final methods is crucial as was pointed out many times in the discussion. The only answer I heard so far was „don’t do OOP“ - but that’s nothing I (and many other) would go with.

Am 28.01.2019 um 20:32 schrieb Ryan Cavanaugh notifications@github.com:

GitHub unhelpfully hides a lot of comments here, so I'm reposting various responses (plus some additions) we've given in the past. Adding some thoughts in below as well.

Some points were considered when we reviewed this last time

CLASSES ARE A JAVASCRIPT FEATURE, NOT A TYPESCRIPT FEATURE. If you really feel like classes are completely useless without final, that is something to take up with the TC39 committee or bring to es-discuss. If no one is willing or able to convince TC39 that final is a must-have feature, then we can consider adding it to TypeScript.
If it's legal to invoke new on something, that has a one-to-one runtime correspondence with inheriting from it. The notion of something that can be new'd but not super'd just doesn't exist in JavaScript
We are trying our best to avoid turning TypeScript into an OOP keyword soup. There are many better composition mechanics than inheritance in JavaScript and we don't need to have every single keyword that C# and Java have.
You can trivially write a runtime check to detect inheritance from a "should-be-final" class, which you really should do anyway if you're "worried" about someone accidently inheriting from your class since not all your consumers might be using TypeScript.
You can write a lint rule to enforce a stylistic convention that certain classes aren't inherited from
The scenario that someone would "accidently" inherit from your should-be-sealed class is sort of an odd one. I'd also argue that someone's individual assessment of whether or not it's "possible" to inherit from a given class is probably rather suspect.
There are basically three reasons to seal a class: Security, performance, and intent. Two of those don't make sense in TypeScript, so we have to consider the complexity vs gain of something that's only doing 1/3rd the job it does in other runtimes.
haven't heard a convincing argument for why TS doesn't need it

Just a reminder that this is not how it works. All features start at -100 https://blogs.msdn.microsoft.com/ericgu/2004/01/12/minus-100-points/. From that post

In some of those questions, the questions asks why we either “took out” or “left out” a specific feature. That wording implies that we started with an existing language (C++ and Java are the popular choices here), and then started removing features until we got to a point where we liked. And, though it may be hard for some to believe, that's not how the language got designed.

We have a finite complexity budget. Everything we do makes every next thing we do 0.1% slower. When you request a feature, you're requesting that every other feature become a little bit harder, a little bit slower, a little bit less possible. Nothing gets in the door here because Java has it or because C# has it or because it'd only be a few lines of code or you can add a commandline switch. Features need to pay for themselves and for their entire burden on the future.

What would change our mind here? People showing up with examples of code that didn't work the way it should have because the final keyword was missing.

The scenario here that the keyword attempts to address is one where someone mistakenly inherits from a class. How do you mistakenly inherit from a class? It's not an easy mistake to make. JavaScript classes are inherently fragile enough that any subclass must understand its base class's behavioral requirements, which does mean writing some form of documentation, and the first line of that documentation can simply be "Do not subclass this class". Or there can be a runtime check. Or the class constructor can be hidden behind a factory method. Or any other number of options.

In other words: the only correct process for inheriting from a class in JavaScript always starts with reading that class's documentation. There are too many constraints the base class might have to simply derive and start coding. If, at the very first step, you should already know not to try, what value is the static enforcement providing at step three?

Why must TypeScript have this keyword when the situation is already one in which you should have had encountered at least two separate roadblocks telling you not to be there to begin with?

I've been programming for approximately forever years now and I have not once tried to subclass something only to find out it was sealed and I shouldn't have tried. It's not because I'm a super genius! It's just an extremely uncommon mistake to make, and in JavaScript these situations are already ones where you need to be asking questions of the base class in the first place. So what problem is being solved?

If you're not interested in reinforcing the main feature of Typescript, please say it loud so that tech news can relay that on Twitter

We're very explicit about how we design the language https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals; non-goal number one speaks exactly to this suggestion.

My personal reaction after reading the many comments here is that there's a strong biasing effect due to constructs that exist in other languages. If you just approach this from a blank slate perspective, there are dozens of behavioral you could imagine, of which TypeScript has only a small subset.

You could easily imagine some keyword that was applied to a method and enforced that derived methods called it via super https://github.com/Microsoft/TypeScript/issues/21388. If C# and Java had this keyword, people would absolutely apply that keyword in places where it made sense. In fact, it'd arguably be much more commonly enforced than final, because it's impossible to apply a runtime check for, and is a much more subtle aspect of the base-derived contract than "derived classes may not exist". It'd be useful in a variety of ways that final wouldn't be. I would much rather have that keyword than this one (though I think neither meets the complexity vs value bar).

So, then, what would be an appropriate reason to consider implementing this?

When we look at feedback, strong considerations look like this:

I can't adequately express the behavior of this JavaScript library
My code compiled, but really shouldn't have because it had this (subtle) error
The intent of this code is not adequately communicated by the type system
final hits these, but so would a modifier that says "This function can't be called twice in a row" or "This class's construction doesn't really finish until the next async tick". Not everything imaginable should exist.

We've never seen feedback like "I spent hours debugging because I was trying to inherit from a class that wasn't actually subclassable" - someone saying that would rightly trigger a 'wat' because it's not really imaginable. Even if the modifier existed, it wouldn't really help the situation, because libraries don't document if classes are intended to be final - e.g. is fs.FSwatcher https://nodejs.org/api/fs.html#fs_class_fs_fswatcher final ? It seems like even the node authors don't know. So final is sufficient if the authors know that it's final, but that's going to be documented regardless, and a lack of final doesn't really tell you anything because it's often simply not known either way.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/8306#issuecomment-458268725, or mute the thread https://github.com/notifications/unsubscribe-auth/AA3NA4o9wu54CcJyK1C8WHVjXIQwfmsUks5vH1A8gaJpZM4IP8M3.

Yeah, the discussion is so diffuse now. I would like to see these issues in a tracker where you can vote, like the one used for IntelliJ / webstorm issues. Get some actual numbers of how many people would like to see final for classes and/or methods.

final would be very helpful

An abstract keyword is used to remind programmers that they should override the field. A keyword to remind them not to override it will be helpful in similar way.

Final methods can be crucial to making code more structured and/or secure.

For example, lets say you have an application that supports 3rd party plugins. You can force all plugins to extend an abstract class that implements an interface for any methods they MUST create and contains a few final methods that control the order of operations and allow you to implement hooks between operations. Because of this, your application doesn't need to be aware of the specifics of any individual plugin, and still be aware of some of its internal state.

Without final methods, they could interfere with your functions controlling the order of operations; potentially causing a bug, exploit, or other unexpected behavior within your application. While most situations will have some type of workaround, those workarounds can be complicated, repetitive, and sometimes unreliable; whereas final methods make it one simple solution when the method is defined.

Also, While this example is specific to plugins, it is applicable to other situations as well. (new dev on team, ensuring certain logic is not run out of scope, standardizing how code is run, making sure security based methods are not changed, etc. all could benefit from this)

@RyanCavanaugh While there's a good example with render() wanting final, no one has mentioned the open-closed principle (now acronymed up in second place as part of SOLID).
For writing frameworks that others will use this would be useful.
If not, what is the preferred approach for taking the open-closed approach in Typescript?

I would LOVE a final for methods (and I suppose properties and declarations in general). It's causing real trouble - people keep overriding things accidentally not realising that there was one underneath... breaking stuff...

I also think that final methods would be of great value, and as others have pointed out, all of the arguments against seem to focus on final classes. There was a separate issue (#9264) but it was closed because apparently this issue covers it, but I really don't think it does. Could we either re-open that issue, or properly address it here?

My situation is similar to @0815fox where I have an "inner" version of a method, which is abstract, but I want the "original" version of the method to never be overridden, since it contains important behaviour and I don't want a subclass to override that one.

I find it baffling that no matter the overwhelming support of the community in favor of this feature, it is still being declined by the TypeScript developers...

perhaps a stop gap would be to add a standard to TS-Doc where it would be advised that someone not inherit a particular class or override a particular method. Like a @final annotation or something similar.

Well, what should I add to what all the others, voting for "final", have said... Here is my vote for it.

Agreed. Sure, you can use readonly with methods if you'll treat it like a property of type function, but this workaround can be pretty confusing. Would be nice to have a native solution for this problem.

I think final , especially final method is important in type checking, as it often controls the workflow of the methods, which guarantee the order of method called in Template pattern or other design patterns.

final should be the way to go

Gang - since this ticket is (a) closed, and (b) has a misleading title for the "final method" discussions I decided to create a new ticket focused solely on final methods. Hopefully that will be harder to ignore and may even stimulate some progress. Oh yeah see https://github.com/microsoft/TypeScript/issues/33446

@RyanCavanaugh

complexity vs value bar

Could you elaborate on what is so complex about final? Seems to me like it would be one of the easiest keywords to implement, especially given that keywords such as abstract are already implemented, and a lot of the logic/code could be reused from that.

@vinayluzrao

Just as a random example, currently we say that you can inherit from anything that has a construct signature:

// OK
declare const SomeParent: new() => { a: string };
class A extends SomeParent { }

Obviously a final class is new-able:

function make<T>(ctor: new() => T) {
  return ctor;
}
final class Done {
}
// No problem
const d = make(Done); // d: Done

But now there's a contradiction: We have a thing that's new-able, but isn't legal to put in an extends clause. So one level of indirection defeats final:

function subvertFinal(ctor: new() => any) {
  class Mwhahaah extends ctor {
    constructor() {
      super();
      console.log("I extended a final class! So much for 'types'!")
    }
  }
}
final class Done {
}
subvertFinal(Done);

This already people trips people up on abstract and folks are constantly arguing that abstract classes should have some implicit construct signature that exists for typechecking purposes but isn't actually invokeable or something.

FYI 90% of the time when we say "complexity" in these discussions, we mean conceptual complexity as experienced by users of TypeScript, not complexity of implementation from our side as developers of the product. The latter is something that can be dealt with (conditional types and mapped types are extraordinarily complex from an implementation perspective, for example); conceptual complexity isn't something you can deal with by throwing more and smarter engineers at the product. Every time we add something to the language that causes unexpected or hard-to-explain behavior, it's a mental burden on every user of the language.

I just need final for methods, because on a relatively regular basis, someone overrides (without knowing it) a method underneath, therefore my code doesn't run, and things break in very strange ways, and people don't know why, and it often takes a long time to debug.

@RyanCavanaugh that really clarifies it, thanks. Though, I would like to throw out a thought : it seems to me that the problem in the example you gave arises due to the strong coupling between new and extends, perhaps that is the larger problem, and not final itself. I wonder why this coupling decision was made, and how hard it would be to undo it (not at all implying it should be undone) at this point.

I'm not sure if this might be too tangential a discussion to have here, though.

This is an issue I ran into. Such simple code sample cannot be compiled.
final keyword would PERFECTLY solve it:

abstract class Parent {
    public abstract method(): this;
}

class Child extends Parent {
    public method(): this {
        return new Child();
    }
}

Type 'Child' is not assignable to type 'this'.
'Child' is assignable to the constraint of type 'this', but 'this' could be instantiated with a different subtype of constraint 'Child'.

Playground

can this be reopened please? it is obvious that there's a need for this feature

We've discussed this issue about a dozen times at various meetings and every time decided against it. Reopening this to discuss a thirteenth time is not a good use of our time.

You can disagree with the TypeScript team's decisions, which is fine, but we can't spend all day going around in circles re-re-re-re-re-re-re-deciding decisions that people disagree with. There are literally a thousand other suggestions in this repo that deserve initial consideration before this one gets to be brought up again.

Constructive discussion like @nicky1038's comment is very welcomed here; I would request that people not continually ping this thread for news/reconsideration requests so that we don't have to lock it.

I'm here to support adding final for methods as well. I have a base class with a function called _init_ that I do not want subclasses to be able to override. Final would be the perfect solution.

I also support adding the final keyword for methods.
Nowadays it is a standard in object oriented programming.

Adding final makes sense to me. Especially when building libraries/frameworks for others to use, where you don't want to even allow the risk of overriding a function.

If I'm not mistaken adding final could help to avoid hassle with 'myObj' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'MyClass' errors when working with generics because I could make MyClass final and therefore guarantee there aren't any other subtypes.
Not 100% sure but I think that's how you can solve this issue in Java.

We have the same stuff, we are making a base Component in angular that a lot of other packages can or need to extends as there base.
In that we have things like ngOnChanges or even ngOnInit of angular that only the base class should implement and subclasses shouldn't override these but really implement the ngOnInitReal (or what eer we call it) so we are fully in control when that is called and with what.
Because we can't control this we can only do this through documenation. If one component maker doesn't read this its component can break, or it needs to copy a lot of code from our base to do the same thing..

So without final methods we can't really make a rock solid api contract.

did someone mention

class Fancy {
    readonly secureFoo = (message: string) => {
        console.log(message);
    }
}

did someone mention

class Fancy {
    readonly secureFoo = (message: string) => {
        console.log(message);
    }
}

While you can do this, there are some differences. For example, each instance of the class will have its own copy of the function, as opposed to only the prototype having the function that everyone uses.

Was this page helpful?
0 / 5 - 0 ratings