swc-project/swc

Async creation function requires the usage of `new this()` not `new ClassName()`

tegefaulkes opened this issue · 4 comments

Describe the bug

I have a class that is decorated with a decorator called CreateDestroyStartStop. This augments the class with some logic regarding locking and asynchronous usage. It adds a readonly parameter readonly [initLock]: RWLockWriter; where initLock is a symbol const initLock = Symbol('initLock').

We use a static create method when instantiating our class. This returns a new instance of our class.

  static async createXFails() {
    return new X();
  }

What we've found is that, if this static create function constructs the class using new X() the resulting instance of the class is not properly decorated. Specifically the readonly [initLock]: RWLockWriter; that the decorator should've added is undefined.

Conversely if we construct the class using new this(), the property is properly defined and everything works.

  static async createXWorks() {
    return new this();
  }

It seems that constructing the class with new x() is undecorated while new this() is decorated.

Input code

import { CreateDestroyStartStop, ready } from '@matrixai/async-init/dist/CreateDestroyStartStop';

interface X extends CreateDestroyStartStop {}
@CreateDestroyStartStop()
class X {
  static async createXWorks() {
    return new this();
  }
  static async createXFails() {
    return new X();
  }
  async start() {}
  async stop() {}
  async destroy() {}

  @ready()
  async thing() {}
}


async function mainWorks () {
  const x = await X.createXWorks();
  await x.start()
  await x.thing();
  await x.stop();
  await x.destroy();
}

async function mainFails () {
  const x = await X.createXFails();
  await x.start()
  await x.thing();
  await x.stop();
  await x.destroy();
}

// void mainWorks();
void mainFails();

Config

No response

Playground link

No response

Expected behavior

After using the static createX method to instantiate the class, the readonly [initLock]: RWLockWriter; property added by the decorator should be defined. Any usage of this[initLock] should be defined and any method calls such as this.[initLock].isLocked() should succeed.

Specifically, in the provided code example, calling x.thing() should not throw any errors.

Actual behavior

After using the static create method createXFail(), the property readonly [initLock]: RWLockWriter; added by the decorator is undefined. Calling x.thing() which is a method decorated with @ready() which accesses the readonly [initLock]: RWLockWriter; with this.[initLock].isLocked() results in the following error.


> @matrixai/db@5.1.0 ts-node
> ts-node "test.ts"

TypeError: Cannot read properties of undefined (reading 'isLocked')
    at X.thing (/home/faulkes/matixWorkspace/gitRepos/js-db/node_modules/@matrixai/async-init/src/CreateDestroyStartStop.ts:189:30)
    at mainFails (/home/faulkes/matixWorkspace/gitRepos/js-db/test.ts:44:11)

Version

1.3.59

Additional context

We have a downstream issue for tracking this at MatrixAI/js-async-init#25.

kdy1 commented

Please post your config and playground link

kdy1 commented

I'll reopen once you add them.

I simplified the example a little for the playground. The example in the playground exhibits the problem on my local system. but when I take the compiled code from the playground run it in node I don't get the expected problem.

.swcrc taken from the playground is.

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true 
    },
    "target": "es2021",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}

We don't use a .swcrc file for our project, We've configured ts-node with tsconfig.json.

{
  "compilerOptions": {
    "outDir": "./dist",
    "tsBuildInfoFile": "./dist/tsbuildinfo",
    "incremental": true,
    "sourceMap": true,
    "declaration": true,
    "allowJs": true,
    "strictNullChecks": true,
    "noImplicitAny": false,
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "module": "CommonJS",
    "target": "ES2021",
    "baseUrl": "./src",
    "paths": {
      "@": ["index"],
      "@/*": ["*"]
    },
    "noEmit": true
  },
  "include": [
    "./src/**/*",
    "./src/**/*.json",
    "./tests/**/*",
    "./scripts/**/*",
    "./benches/**/*"
  ],
  "ts-node": {
    "require": ["tsconfig-paths/register"],
    "transpileOnly": true,
    "swc": true
  }
}

I hope this helps.

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.