microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.5k stars 12.43k forks source link

emit an error when using #private fields in their "temporal dead zone" #56810

Open trusktr opened 9 months ago

trusktr commented 9 months ago

🔍 Search Terms

error for private member accessed before ready

✅ Viability Checklist

⭐ Suggestion

This produces a runtime error when you run it:

abstract class Base {
  constructor() {
    this.createStuff()
  }

    abstract createStuff(): void
}

class Sub extends Base {
    #makeSomething() {
        console.log('make stuff')
    }

    createStuff() {
        this.#makeSomething()
    }
}

// TypeError: Cannot read private member from an object whose class did
// not declare it (JavaScript points finger at you while laughing)
new Sub().createStuff()

playground

📃 Motivating Example

It has happened to me too many times due to subclass code running early before initialization of private fields.

Unfortunately the simple solution is to avoid class fields for this case (use properties-defined-in-constructor, or getters).

💻 Use Cases

Sometimes we want a constructor to make stuff generically for subclasses, but then subclasses should override or implement some methods to replace certain parts.

I believe there is enough static info that it would be possible to check for this case and emit a compile error.

Similarly it would also be possible to enforce non-private fields to be | undefined when used early in a location that is known to be called before initialization (f.e. in a superclass constructor).

fatcerberus commented 9 months ago

Related to #9998 because this would require control flow analysis to recursively follow function/method/constructor calls.

fatcerberus commented 9 months ago

A rule that might work without requiring deep CFA would be to completely disallow calling abstract methods in the constructor, under the assumption that they could make use of private fields in a subclass. That might be too breaky, though, and also wouldn’t catch non-abstract calls that are overridden in a subclass.

RyanCavanaugh commented 9 months ago

The C++ wisdom from the 90's was that you should never invoke any virtual behavior from the constructor (precisely because the derived class constructor hasn't run yet), abstract being the worst-case scenario for that rule, but this was never enforced by the compiler, presumably for the same CFA inlining reasons. Kids these days, amirite?