Skip to content

Conversation

@han-tyumi
Copy link
Contributor

This fixes an issue where the extension fails to find ReScript platform binaries (like bsc.exe) when using package managers that use symlinked node_modules structures, such as Deno and pnpm.

The issue occurs because path.join(rescriptDir, "..") performs string manipulation rather than following the symlink. When node_modules/rescript is a symlink (e.g., to .deno/rescript@12.0.0/node_modules/rescript), the parent directory resolves to node_modules/ instead of the actual parent where @rescript/darwin-arm64 etc. are located.

Using fs.realpathSync() to resolve the symlink first ensures the correct parent directory is found.

This fixes an issue where the extension fails to find ReScript platform
binaries (like bsc.exe) when using package managers that use symlinked
node_modules structures, such as Deno and pnpm.

The issue occurs because `path.join(rescriptDir, "..")` performs string
manipulation rather than following the symlink. When `node_modules/rescript`
is a symlink (e.g., to `.deno/rescript@12.0.0/node_modules/rescript`),
the parent directory resolves to `node_modules/` instead of the actual
parent where `@rescript/darwin-arm64` etc. are located.

Using `fs.realpathSync()` to resolve the symlink first ensures the
correct parent directory is found.
@nojaf
Copy link
Member

nojaf commented Nov 29, 2025

Hello,

I'm curious about this change.
Did you test this locally?

What does your lib/bs/compiler-info.json look like?

Can you do a dump of the lsp server state? What do you see there?

@han-tyumi
Copy link
Contributor Author

han-tyumi commented Nov 29, 2025

Hello,

I'm curious about this change. Did you test this locally?

What does your lib/bs/compiler-info.json look like?

Can you do a dump of the lsp server state? What do you see there?

Yes, I patched the extension locally and have been developing with it without issues.

Here's my lib/bs/compiler-info.json:

{
  "version": "12.0.0",
  "bsc_path": "/Users/han-tyumi/Code/javascript/rescript/gracefully-hooked/node_modules/.deno/@rescript+darwin-arm64@12.0.0/node_modules/@rescript/darwin-arm64/bin/bsc.exe",
  "bsc_hash": "b9ee425ca842260e47bee75b5d05fce7c14e3fa37f1206b07efd3c889bad3b33",
  "rescript_config_hash": "a1cbd8ce74ea4ffa41e8e58825182151180230d5c658f58180ac101390dde946",
  "runtime_path": "/Users/han-tyumi/Code/javascript/rescript/gracefully-hooked/node_modules/.deno/@rescript+runtime@12.0.0/node_modules/@rescript/runtime",
  "generated_at": "1764397204220"
}

And the LSP Server State:

{
  "config": {
    "askToStartBuild": true,
    "inlayHints": {
      "enable": false,
      "maxLength": 25
    },
    "codeLens": false,
    "signatureHelp": {
      "enabled": true,
      "forConstructorPayloads": true
    },
    "incrementalTypechecking": {
      "enable": true,
      "acrossFiles": false,
      "debugLogging": false
    },
    "cache": {
      "projectConfig": {
        "enable": true
      }
    },
    "binaryPath": null,
    "platformPath": null,
    "runtimePath": null,
    "compileStatus": {
      "enable": true
    }
  },
  "projects": [],
  "workspaceFolders": [
    "/Users/han-tyumi/Code/javascript/rescript/gracefully-hooked"
  ]
}

@nojaf
Copy link
Member

nojaf commented Nov 29, 2025

The fact that "bsc_path": "/Users/han-tyumi/Code/javascript/rescript/gracefully-hooked/node_modules/.deno/@rescript+darwin-arm64@12.0.0/node_modules/@rescript/darwin-arm64/bin/bsc.exe" looks correct, I would expect that path being used to detect the binaries.

Are you on the latest version of the extension?

If you are, I would try and troubleshoot why

if (projectRootPath !== null) {
try {
const compilerInfo = path.resolve(
projectRootPath,
c.compilerInfoPartialPath,
);
const contents = await fsAsync.readFile(compilerInfo, "utf8");
const compileInfo = JSON.parse(contents);
if (compileInfo && compileInfo.bsc_path) {
const bsc_path = compileInfo.bsc_path;
if (binary === "bsc.exe") {
return bsc_path;
} else {
const binary_path = path.join(path.dirname(bsc_path), binary);
return binary_path;
}
}
} catch {}
}
is not working.

@han-tyumi
Copy link
Contributor Author

Yes, I'm on version 1.70.0 of the extension.

You're right that when compiler-info.json exists, the extension uses bsc_path directly and everything works. My fix is for the fallback code path when compiler-info.json doesn't exist yet (e.g., fresh project before first build).

To reproduce, I reverted my fix locally, deleted lib/bs/compiler-info.json, and reloaded VS Code:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/han-tyumi/Code/javascript/rescript/gracefully-hooked/node_modules/@rescript/darwin-arm64/bin.js'

The extension tries to import from node_modules/@rescript/darwin-arm64/bin.js, but with Deno's symlinked structure the actual location is node_modules/.deno/@rescript+darwin-arm64@12.0.0/node_modules/@rescript/darwin-arm64/bin.js.

Since node_modules/rescript is a symlink (-> .deno/rescript@12.0.0/node_modules/rescript), path.join(rescriptDir, "..") performs string manipulation rather than resolving through the symlink, resulting in the wrong parent directory.

Using fs.realpathSync(rescriptDir) first resolves the symlink to its actual location, so the .. traversal finds the correct directory. This only affects the fallback path—once the project is built and compiler-info.json exists, that path is used instead.

@nojaf
Copy link
Member

nojaf commented Nov 30, 2025

Hmm, I see.

I'm in a bun project and using isolated linking and this indeed is a problem if I do not have a compiler-info.json.

Using fs.realpathSync would solve that problem in my case.

I think the more proper fix here would be to have invoke the rescript binary and return the compiler-info.json if it does not exist.

We can use the symlink for the time being.

Thanks

@nojaf nojaf merged commit 2ab39a6 into rescript-lang:master Nov 30, 2025
6 checks passed
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

Successfully merging this pull request may close these issues.

2 participants