Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested generics produce a syntax error if not parenthesized #194

Open
demurgos opened this issue Mar 6, 2025 · 5 comments
Open

Nested generics produce a syntax error if not parenthesized #194

demurgos opened this issue Mar 6, 2025 · 5 comments

Comments

@demurgos
Copy link

demurgos commented Mar 6, 2025

I have some complex TypeScript with a generic type argument which is itself generic. The TypeScript compiler supports it for many years (at least TS 3.x), and I verified that it works with the --erasableSyntaxOnly flag from TS 5.8.

I would expect it to work with Node's builtin type stripper then, but I get an error. Here is a reduced code sample exhibiting the error. I marked the line with the error:

class Wrapper<Fn extends (...args: any[]) => any> {
  #inner: Fn;

  constructor(inner: Fn) {
    this.#inner = inner;
  }

  wrap(x: unknown): ReturnType<Fn> {
    return this.#inner(x);
  }
}

async function main(): Promise<void> {
  // The error happens here:
  const promiseWrapper = new Wrapper<<T>(x: T) => Promise<T>>(Promise.resolve.bind(Promise));
  
  const strPromise: Promise<string> = promiseWrapper.wrap("Hello, World!") as any;
  const str = await strPromise;
  console.log(str);
}

main();

Here is the error that I get with Node 23.9.0:

node:internal/modules/typescript:69
        throw new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
              ^

SyntaxError [ERR_INVALID_TYPESCRIPT_SYNTAX]:   x Expected a semicolon
    ,-[14:1]
 11 | }
 12 | 
 13 | async function main(): Promise<void> {
 14 |   const promiseWrapper = new Wrapper<<T>(x: T) => Promise<T>>(Promise.resolve.bind(Promise));
    :                                                               ^^^^^^^
 15 |   const strPromise: Promise<string> = promiseWrapper.wrap("Hello, World!");
 16 |   const str = await strPromise;
 17 |   console.log(str);
    `----

    at parseTypeScript (node:internal/modules/typescript:69:15)
    at processTypeScriptCode (node:internal/modules/typescript:129:42)
    at stripTypeScriptModuleTypes (node:internal/modules/typescript:196:22)
    at ModuleLoader.<anonymous> (node:internal/modules/esm/translators:547:16)
    at #translate (node:internal/modules/esm/loader:490:12)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:537:27)
    at async ModuleJob._link (node:internal/modules/esm/module_job:116:19) {
  code: 'ERR_INVALID_TYPESCRIPT_SYNTAX'
}

A workaround is to add parenthesis:

- const promiseWrapper = new Wrapper<<T>(x: T) => Promise<T>>(Promise.resolve.bind(Promise));
+ const promiseWrapper = new Wrapper<(<T>(x: T) => Promise<T>)>(Promise.resolve.bind(Promise));

Since there's an available workaround, it's not a high priority for me; but I'm sharing it as it's a relatively unusual piece of code so I don't expect it to be fully tested. I'm reporting it since I found the issue with Node's type stripper, if it's not the right place please let me know where I should post.

It may also be that the syntax above is somehow ambiguous and won't be ever supported, but maybe the error could be clearer in this case.

EDIT

I also found that simply spacing the brackets works:

const promiseWrapper = new Wrapper< <T>(x: T) => Promise<T> >(Promise.resolve.bind(Promise));

The issue is probably in tokenization between nested generics and bit-shift operators.

@marco-ippolito
Copy link
Member

marco-ippolito commented Mar 6, 2025

Thanks for your report this is like to be a swc bug. cc @kdy1
You might want to open an issue in swc https://github.com/swc-project/swc

@kdy1
Copy link
Member

kdy1 commented Mar 6, 2025

cc @magic-akari Can you take a look?

@demurgos
Copy link
Author

demurgos commented Mar 6, 2025

I found some issues in SWC that are related. There are even some linked PRs, but the issue is still present in the version used by Node.

SWC has a bot to auto-close issues which I find very user hostile, so for now I'll leave the issue here. The first issue there would be the perfect one to discuss this, but the fact that it was auto-closed before being fully resolved sends mixed signals.

@magic-akari
Copy link

Thank you for the heads-up! I’ve noted the issue and will prioritize fixing it as soon as possible.

@demurgos
Copy link
Author

demurgos commented Mar 7, 2025

Thank you for the quick PR. I'll keep checking as new Node versions get released and I'll close the ticket when the fix is released on Node current.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants