Skip to content

Keep a namespaced package's dev modules out of its namespace - #9301

Draft
cknitt wants to merge 3 commits into
masterfrom
dev-namespace-overlay
Draft

cknitt wants to merge 3 commits into
masterfrom
dev-namespace-overlay

Conversation

@cknitt

@cknitt cknitt commented Nov 14, 2026

Copy link
Copy Markdown
Member

Fixes #9089 (and #6679). Replaces #9090 with the design discussed there.

Problem

A namespaced package's namespace map listed every module, including its dev modules ("type": "dev" sources). A package that wrote open Provider therefore got Provider's dev modules in scope, and they shadowed its own modules of the same name, typically test helpers like Config. The namespace map also depended on the dev modules, so a dev test that used the package's namespace entry was a dependency cycle.

Change

  • Provider.mlmap now lists only the regular modules. Everyone sees one Provider.cmi.
  • The dev modules get a second map, Provider-dev, which only the package's own dev modules open. bsc takes the alias targets from the map's file name, so the build system writes it as lib/rescript/dev/Provider.mlmap and compiles it with -o Provider-dev.
  • New internal bsc flag -bs-open-alias-map <module>: opens the module without recording its interface CRC, as -bs-ns already does for the namespace. Without it, adding a dev module changes Provider-dev.cmi, and a dev module compiled against the previous version then fails with "inconsistent assumptions over interface Provider-dev".
  • The module graph follows the same rule, so it no longer sees edges the compiler can't resolve. A namespaced package's dev modules are visible only to the dev modules of that package. This also removes the namespace-entry cycle.
  • Editor analysis opens Provider-dev as well, so completion keeps working in dev files.
  • --prod builds remove the Provider-dev artifacts.

Breaking

Another package, or a non-dev module of the same package, can no longer use a namespaced package's dev modules. Previously this worked for local workspace packages, e.g. Provider.TestUtils from a sibling package's tests. The error is the usual "The module or file Provider.TestUtils can't be found". Shared test helpers have to move to a regular source directory. Packages without a namespace are unchanged.

Tests

  • rewatch/tests/compile/24-dev-modules-stay-in-their-package.sh: workspace with two namespaced packages that both have a dev Config. It covers:

    • the Dev dependencies interacting #9089 repro;
    • adding a dev module and then compiling a dev module that reads an older one (the CRC case);
    • both kinds of forbidden access;
    • --prod;
    • a dev test that uses the namespace entry.

    On master it fails at the first check. The CRC check fails if the flag is replaced by a plain -open.

  • Unit tests for the dev map membership, namespace_args, and the graph's visibility rule.

  • tests/analysis_tests/tests-namespaced-references: a dev source directory with hover and completion queries. Completion of a dev module is empty without the analysis change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kbf3gHAzsdStoCEpBgx2tS

Why:
    The namespace map aliased every module of a package, its dev modules
    too. A package that opened a dependency's namespace got the
    dependency's dev modules in scope, which shadowed its own modules of
    the same name (#9089, #6679). The namespace map also depended on the
    dev modules, so a dev test that used the namespace entry was a
    dependency cycle.

    The namespace map now holds only the regular modules. The dev modules
    get a second map, Ns-dev, which only the package's own dev modules
    open. bsc takes the alias targets from the map's file name, so it is
    dev/Ns.mlmap, compiled with -o Ns-dev. The new bsc flag
    -bs-open-alias-map opens it without recording its interface CRC, as
    -bs-ns does for the namespace; otherwise adding a dev module would
    break every dev module compiled against the previous dev map. The
    module graph and the editor analysis follow the same rule.

Diagnostic removed (open of a dependency's namespace):
Example:
    // provider/test/Config.res (dev): let other = 2
    // consumer/test/Config.res (dev): let get = () => 3
    // consumer/test/Check.res (dev):
    open Provider
    let value = Config.get()
Before (diagnostic):
    The value get can't be found in Config

Diagnostic removed (dev test using the namespace entry):
Before (diagnostic):
    Can't continue... Found a circular dependency in your code:
    @Provider (provider namespace map)
     → Provider.Local (provider/test/Local.res)
     → Entry (provider/src/Entry.res)
     → @Provider (provider namespace map)

Representation modified (namespace maps of provider):
Before (text):
    lib/rescript/Provider.mlmap: Config Local Public
After (text):
    lib/rescript/Provider.mlmap: Public
    lib/rescript/dev/Provider.mlmap (compiled to Provider-dev): Config Local

Breaking: Another package, or a non-dev module of the same package, can
    no longer use a namespaced package's dev modules ("The module or file
    Provider.Config can't be found"). Move shared test helpers to a
    regular source directory. Packages without a namespace are unchanged.

Review: Why a compiler flag and not a graph node for the dev map? The dev
    map only holds aliases, whose targets are recorded themselves, so its
    CRC does not need recording, the same reasoning as for -bs-ns. A
    graph node would recompile every dev module whenever one is added.

Fixes #9089

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gHAzsdStoCEpBgx2tS
Signed-off-by: Christoph Knittel <ck@cca.io>
@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Changelog

💥 Breaking Change

  • Keep a namespaced package's dev modules out of its namespace, so that open of a dependency's namespace no longer shadows your own modules. Dev modules are now only visible to the dev modules of their own package; move test helpers that other packages or non-dev code use to a regular source directory.

Semantic Diff

3 commits · Full page

Keep a namespaced package's dev modules out of its namespace

a559fd0 · cknitt + Claude · Nov 14, 2026, 08:57 UTC

Why

The namespace map aliased every module of a package, its dev modules too. A package that opened a dependency's namespace got the dependency's dev modules in scope, which shadowed its own modules of the same name (#9089, #6679). The namespace map also depended on the dev modules, so a dev test that used the namespace entry was a dependency cycle.

The namespace map now holds only the regular modules. The dev modules get a second map, Ns-dev, which only the package's own dev modules open. bsc takes the alias targets from the map's file name, so it is dev/Ns.mlmap, compiled with -o Ns-dev. The new bsc flag -bs-open-alias-map opens it without recording its interface CRC, as -bs-ns does for the namespace; otherwise adding a dev module would break every dev module compiled against the previous dev map. The module graph and the editor analysis follow the same rule.

Diagnostic removed (open of a dependency's namespace)

// provider/test/Config.res (dev): let other = 2
// consumer/test/Config.res (dev): let get = () => 3
// consumer/test/Check.res (dev):
open Provider
let value = Config.get()

diagnostic · Before

The value get can't be found in Config

Diagnostic removed (dev test using the namespace entry)

diagnostic · Before

Can't continue... Found a circular dependency in your code:
@Provider (provider namespace map)
 → Provider.Local (provider/test/Local.res)
 → Entry (provider/src/Entry.res)
 → @Provider (provider namespace map)

Representation modified (namespace maps of provider)

text

-lib/rescript/Provider.mlmap: Config Local Public
+lib/rescript/Provider.mlmap: Public
+lib/rescript/dev/Provider.mlmap (compiled to Provider-dev): Config Local

Fixes #9089

⚠️ Breaking: Another package, or a non-dev module of the same package, can no longer use a namespaced package's dev modules ("The module or file Provider.Config can't be found"). Move shared test helpers to a regular source directory. Packages without a namespace are unchanged.

Review

  • Why a compiler flag and not a graph node for the dev map? The dev map only holds aliases, whose targets are recorded themselves, so its CRC does not need recording, the same reasoning as for -bs-ns. A graph node would recompile every dev module whenever one is added.
Give the output namespace and the alias maps separate flags fields

ebf93b7 · cknitt + Claude · Nov 14, 2026, 09:10 UTC

Clflags.dont_record_crc_unit did two jobs: it named the outputs (Config-Ns) and kept the namespace's CRC out of the recorded imports. It is now Clflags.namespace, for the names only. -bs-ns adds the namespace to Clflags.alias_maps like -bs-open-alias-map, and Env.imports skips exactly the alias maps. The command line and the outputs are unchanged.

✨ Open the dev map in the editor analysis for dev files only

80e2db1 · cknitt + Claude · Nov 14, 2026, 09:19 UTC · new

Codex review: the analysis opened the dev map for every file of the package, so completion in a regular module suggested dev modules that the compiler now rejects there. The package keeps the dev map's open apart, and get_package adds it only for a file in a dev source directory, as the compiler does.

@cknitt

cknitt commented Nov 14, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-14T09:23:16.123612Z 80e2db1 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a559fd009d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread analysis/src/packages.ml Resolved
@codecov

codecov Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 82.08%. Comparing base (9523348) to head (80e2db1).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
analysis/src/packages.ml 94.44% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9301      +/-   ##
==========================================
+ Coverage   82.06%   82.08%   +0.02%     
==========================================
  Files         455      455              
  Lines       65399    65414      +15     
==========================================
+ Hits        53672    53698      +26     
+ Misses      11727    11716      -11     
Files with missing lines Coverage Δ
analysis/src/shared_types.ml 83.68% <ø> (ø)
compiler/bsc/rescript_compiler_driver.ml 80.98% <100.00%> (+0.06%) ⬆️
compiler/core/config_util.ml 100.00% <100.00%> (ø)
compiler/core/js_implementation.ml 85.21% <ø> (ø)
compiler/core/js_packages_state.ml 76.47% <100.00%> (ø)
compiler/ml/clflags.ml 75.00% <ø> (ø)
compiler/ml/env.ml 80.14% <100.00%> (-0.07%) ⬇️
analysis/src/packages.ml 82.75% <94.44%> (+1.95%) ⬆️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

✅ Compiler benchmarks

Allocated words

In total: 640,953,103 → 641,035,161 (+0.01%).

Generated projects: 920,898,114 → 921,049,054 (+0.02%).

None of the 265 benchmarks changed by 0.1% or more.

Instructions

In total: 9,209,713,560 → 9,209,805,733 (±0.00%).

None of the 33 benchmarks changed by 0.1% or more.

Output size

In total: 25,843,406 → 25,843,157 (±0.00%).

None of the 13 benchmarks changed by 0.1% or more.

Pull request 80e2db1 compared with base 1efa212 in this run.

@pkg-pr-new

pkg-pr-new Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@9301

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@9301

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@9301

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@9301

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@9301

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@9301

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@9301

commit: 80e2db1

cknitt and others added 2 commits November 14, 2026 09:10
Clflags.dont_record_crc_unit did two jobs: it named the outputs
(Config-Ns) and kept the namespace's CRC out of the recorded imports. It
is now Clflags.namespace, for the names only. -bs-ns adds the namespace
to Clflags.alias_maps like -bs-open-alias-map, and Env.imports skips
exactly the alias maps. The command line and the outputs are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gHAzsdStoCEpBgx2tS
Signed-off-by: Christoph Knittel <ck@cca.io>
Codex review: the analysis opened the dev map for every file of the
package, so completion in a regular module suggested dev modules that
the compiler now rejects there. The package keeps the dev map's open
apart, and get_package adds it only for a file in a dev source
directory, as the compiler does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gHAzsdStoCEpBgx2tS
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt

cknitt commented Nov 14, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80e2db1e97

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread analysis/src/packages.ml
Comment on lines +138 to +140
let regular = directories ~include_dev:false in
directories ~include_dev:true
|> List.filter (fun directory -> not (List.mem directory regular))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve dev classification for overlapping source directories

With sources configured as [{"dir":"src/test","type":"dev"},{"dir":"src","subdirs":true}], the compiler discovers src/test first and retains its modules' dev classification (Source.scan_source and Traversal_coverage.admit skip rediscovering files). However, the recursive regular-directory enumeration also contains src/test, so this subtraction removes it from dev_source_directories. Consequently, for_file never opens the dev map for those dev files. Since this change also removes dev aliases from the regular namespace, completion for a sibling dev module such as Helper. stops working even though the project compiles. Determine dev-directory ownership using the compiler's ordered source classification rather than subtracting directory sets.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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.

Dev dependencies interacting

1 participant