TypeScript constructors are incompatible with ES6 constructors

Topics: General, Language Specification
Jul 25, 2013 at 2:47 PM
Two things are worrying me about TypeScript constructors that seems to make them incompatible with ES6.

The current ES6 says: If a constructor function needs to perform superclass initialization, it must do so by explicitly calling super(/arguments/) at some point within the constructor body. - http://tc39wiki.calculist.org/es6/classes/

So...

Calling super is currently required

The proposal is not very clear about this leaving some room for interpretation, but it seems to make calling 'super()' optional, and up to the child class to decide if it needs to perform super class initialization or not. I would be really surprised if browsers enforced that. This is not a big issue, but makes mandatory to execute code that would not be required to do so, creating possible performance issues for TypeScript code. Some libraries currently allow you to override the constructors at the runtime, and not call the existing constructor at all (http://backbonejs.org/#Model-constructor).

Calling 'super()' is required as the first statement of the constructor

This is a big issue. The proposal is very clear on this, where if super initialization is required, it must be done at some point of the child constructor, not at the beginning.

This is a real issue, because libraries out there use this feature of the language and will continue using them because it creates a nice pattern. You can optionally define properties that will be used by the constructor, and then call the constructor without worring about lots of optional arguments and their positions.

This "feature" makes TypeScript classes incompatible with existing libraries, and require workarounds to make them work correctly, like running initialization code twice.

I have been working with C# for the last 10 years, and have coded in other OO languages as well, and I understand why it would be good to have this added to the language. I don't want to discuss if this works in other platforms, because I know they work there and provide required safeties to the code. Also, I'm not saying Backbone or other common JS libraries have exemplary architecture designs.

The point here is that these checks are enforcing the runtime to execute code that shouldn't run or that should run in a different order, and there is no way to prevent that.

Some ideas to solve this:
  • TypeScript could enforce this based on a compiler option if the project requires the check, just like "noImplicitAny"
  • TypeScript should not enforce this by default, but show warnings if you forget to call super or call it somewhere else
  • TypeScript could not enforce these checks based on compiler option
I must say that I vote against option 3 (enforcing by default like today) because code that produces different behavior than what you get with JavaScript should be optional, not mandatory.
Jul 25, 2013 at 4:38 PM
This is a good point to consider. However, keeping in mind that not all JavaScript compiles as TypeScript out of the box anyway, I tend to prefer having the safer option be the default, and I'm less concerned with compatibility. Most existing javascript code has to be ported to TypeScript anyway to avoid type errors due to inferred structural types.

I'm not sure that skipping super constructors is a safe optimization to make anyway, given the possibility that the super class's constructor could be modified at some point in the future (possibly at runtime even). And, assuming it WAS safe to do, the compiler is the appropriate place to make that optimization. That is, the compiler would check if your base class's constructor did anything, and if so, removed the super constructor call. That would at least eliminate the possibility of your (pre)optimization breaking in the future when the base class changes.
Coordinator
Jul 25, 2013 at 8:20 PM
@nvivo: Two things to consider:
1) Not calling super() is quite dangerous from a versioning perspective, and I have argued that this requirement should be part of ES6. However, ECMAScript generally doesn't have these kind of restrictions though, so this has not been added to the ES6 proposal. The TypeScript position is intentionally being more conservative here, as is done in many places in TypeScript.
2) TypeScript does not require 'super' to be the first statement in the constructor. This constraint only applies when there are field initializers in the class, which is not part of ES6, so there should be no practical conflict. We may consider loosening this restriction further in the future, but that needs to be considered in context of the specific rules for what field initializers means (exactly where in the constructor do they get emitted, and can a super constructor depend on this state being initialized).