Skip to content

Uninitialized instance properties can be accessed via initializersΒ #57984

@igrep

Description

@igrep

πŸ”Ž Search Terms

  • "constructor instance variable undefined"
  • "instance property undefined constructor uninitialized"

πŸ•— Version & Regression Information

> npx tsc --version
Version 5.4.3

⏯ Playground Link

https://www.typescriptlang.org/play?noUncheckedIndexedAccess=true&ts=5.5.0-dev.20240328#code/MYGwhgzhAECiAeYC2AHEBTaBvAUNaYAXNBAC4BOAlgHYDmA3HtAEbQC80pAFpRAHQBXCOgCCACgCUjfMAD21MuQHBSs8mKIkKNWhOxN83XnzDsC06AF8cTIaMnFFO-fnzl0pAeWqce-MHyqAKooKOjkAMKQ6JIW1tY4cgqyGHwgsrRi1OgA7nCIqBhiAOToBWjoxRJ8zFJAA

πŸ’» Code

tsconfig.json:

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true
  }
}

example.ts:

class Example {
  a: string;
  b = this.useA();
  constructor(a: string) {
    this.a = a;
  }

  useA(): string {
    return this.a.toUpperCase();
  }
}

console.log(new Example('example').b);

πŸ™ Actual behavior

Produces no compile error, and generates a code that throws a runtime error:

TypeError: Cannot read properties of undefined (reading 'toUpperCase')

πŸ™‚ Expected behavior

Produces a compile error similar to "Property 'a' is used before its initialization."

Additional information about the issue

I'm not sure whether this may be a issue that can be fixed or just keep it as is "by design". I haven't found any other issues similar to this one here, at least with the search terms I tried.

As you know, we can easily modify the example case like below to avoid the error:

class Example {
  b = this.a;
  constructor(public a: string) {
    this.a = a;
  }

  useA(): string {
    return this.a.toUpperCase();
  }
}

console.log(new Example('example').b);

But we can't adopt this workaround when the property is a hard-private one:

class Example {
  #privA: string;
  #privB = this.useA();
  constructor(privA: string) {
    this.#privA = privA;
  }

  useA(): string {
    return this.#privA.toUpperCase();
  }

  get a(): string {
    return this.#privA;
  }

  get b(): string {
    return this.#privB;
  }
}

console.log(new Example('example').b);

Actually I found this problem when using a hard-private variable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions