Skip to content

Bug: __runInitializers(this) is emitted before super() call #53204

@chharvey

Description

@chharvey

Bug Report

🔎 Search Terms

Typescript 5, decorator, emit, __runInitializers, super, constructor

🕗 Version & Regression Information

Version v5.1.0-dev.20230310 (nightly)

  • This changed between versions v4.9.5 and v5.0.0-rc
  • I was unable to test this on prior versions because the new decorator spec was not supported

⏯ Playground Link

Playground link with relevant code

💻 Code

class Foo {}

class Bar extends Foo {
	public constructor() {
		console.log('Entering constructor.');
		super();
		console.log('Exiting constructor.');
	}

	@loggedMethod
	public greet(): void {
		console.log('Hello!');
	}
}

function loggedMethod(method: any, _context: any): any {
	return function (this: any) {
		console.log('Entering method.');
		method.call(this);
		console.log('Exiting method.');
	};
}

new Bar(); /* JS Error:
Must call super constructor in derived class before
accessing 'this' or returning from derived constructor */

🙁 Actual behavior

The emitted output includes the following (abbreviated for clarity; see entire output in playground):

// ...
class Bar extends Foo {
	// ...
	constructor() {
		__runInitializers(this, _instanceExtraInitializers); // <-- here!
		console.log('Entering constructor.');
		super();
		console.log('Exiting constructor.');
	}
	greet() {
		console.log('Hello!');
	}
}

__runInitializers(this, _instanceExtraInitializers); was emitted before the call to super(); in the constructor. This is a problem because this cannot be referenced before super() is called. The following error is thrown:

Must call super constructor in derived class before accessing 'this' or returning from derived constructor.

🙂 Expected behavior

I expect the call to __runInitializers to be emitted after super:

constructor() {
	console.log('Entering constructor.');
	super();
	__runInitializers(this, _instanceExtraInitializers);
	console.log('Exiting constructor.');
}

🔄 Workaround

Add a private field. Private fields are initialized after super(), so the location where __runInitializers is emitted isn’t a problem.

Source:

class Bar extends Foo {
	readonly #HACK = 0;

	public constructor() {
		console.log('Entering constructor.');
		super();
		console.log('Exiting constructor.');
	}

	// ...
}

Output:

// ...
class Bar extends Foo {
	// ...
	#HACK = (__runInitializers(this, _instanceExtraInitializers), 0);
	constructor() {
		console.log('Entering constructor.');
		super();
		console.log('Exiting constructor.');
	}
	// ...
}

(Note/Reminder: Even though the field is written above the constructor, it isn’t executed until after the super() call.)

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions