Skip to content

Fix dev module shadowing - #9090

Open
CarlOlson wants to merge 3 commits into
rescript-lang:masterfrom
CarlOlson:fix/master-dev-module-shadowing
Open

CarlOlson wants to merge 3 commits into
rescript-lang:masterfrom
CarlOlson:fix/master-dev-module-shadowing

Conversation

@CarlOlson

Copy link
Copy Markdown

Checklist

  • Added a changelog fragment (see changes/README.md), or no release notes are needed and a maintainer should apply changelog:skip.
  • Added tests for the fix or feature.
  • Signed off every commit for the DCO (git commit -s).

Signed-off-by: CarlOlson <CarlOlson@users.noreply.github.com>
Fix the review finding where a namespace entry compiled against dev aliases produced an interface incompatible with the exported namespace. Keep the internal namespace first for ordinary modules, and cover entry consumers with a clean and incremental build.

Signed-off-by: CarlOlson <CarlOlson@users.noreply.github.com>
Why:
    Dependency dev aliases can shadow local test modules after opening a
    dependency namespace. Match the Rust fix while keeping namespace-entry
    interfaces compatible with the namespace exported to consumers.

Representation modified (namespace interfaces):
Before (text):
    Local modules and consumers use the same namespace, including dev aliases.
After (text):
    Ordinary local modules use the full namespace in lib/bs.
    Consumers and namespace entries use the filtered namespace in lib/ocaml.

Preserve internal namespace invalidation across publication failures. Add
include-precedence assertions and a changelog fragment. Validation passed:
48 OCaml unit tests and the full shared build-system suite, including the
dev-module and namespace-entry regressions.

Signed-off-by: CarlOlson <CarlOlson@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Changelog

🐛 Bug fix

  • Keep dependency dev modules out of exported namespaces, while preserving local dev access and compatible namespace-entry interfaces.

Semantic Diff

3 commits · Full page

Fix dev-module shadowing across workspace namespaces

81d01c7 · CarlOlson · Oct 27, 2026, 14:04 UTC

Compile namespace entries against the public namespace

d71a9fb · CarlOlson · Oct 27, 2026, 14:20 UTC

Fix the review finding where a namespace entry compiled against dev aliases produced an interface incompatible with the exported namespace. Keep the internal namespace first for ordinary modules, and cover entry consumers with a clean and incremental build.

Isolate dependency dev namespaces in OCaml rewatch

c4a7b26 · CarlOlson · Oct 27, 2026, 14:29 UTC

Why

Dependency dev aliases can shadow local test modules after opening a dependency namespace. Match the Rust fix while keeping namespace-entry interfaces compatible with the namespace exported to consumers.

Representation modified (namespace interfaces)

text

-Local modules and consumers use the same namespace, including dev aliases.
+Ordinary local modules use the full namespace in lib/bs.
+Consumers and namespace entries use the filtered namespace in lib/ocaml.

Preserve internal namespace invalidation across publication failures. Add include-precedence assertions and a changelog fragment. Validation passed: 48 OCaml unit tests and the full shared build-system suite, including the dev-module and namespace-entry regressions.

@cknitt

cknitt commented Oct 29, 2026

Copy link
Copy Markdown
Member

Note: the analysis below was done by Claude: Fable 5.1, building on a first review by Opus 5.5. I've read it and agree with the direction, but I haven't checked every detail myself yet.

Thanks a lot for tracking this down and fixing both build systems! The diagnosis is right: the namespace module aliases the package's dev modules, so open Provider in a dependent package brings Provider's Config into scope and shadows the consumer's own Config.

I'm hesitant about the two-map approach, though. It compiles two different Provider.cmi interfaces under the same module name. That only works because modules compiled with -bs-ns don't record the namespace's CRC, and because -I . comes before -I ../ocaml. The namespace entry needs a special case for the same reason.

I think one case still breaks: a namespace-entry package that has dev modules, where a test uses the entry module (Provider.Public.make()). The test sees the full namespace through -bs-ns. It then loads the entry's .cmi, which was compiled against the filtered namespace, so the build should fail with "inconsistent assumptions over interface". I haven't run it, but the test in this PR doesn't cover that case.

An alternative that avoids two versions of one interface: a separate dev-only namespace.

  • Provider.mlmap lists only the non-dev modules. There is then a single Provider.cmi, and the entry case stays as it is today.
  • A second map lists only the dev modules. It's compiled with -o Provider-dev: the unit name comes from -o, while the alias targets still come from the map's file name Provider.mlmap, so they point at Config-Provider etc. No compiler change is needed.
  • Dev sources are compiled with -bs-ns Provider -open Provider-dev, and only when the package has dev modules.

This makes dev modules private to their package. Another package can no longer reach them as Provider.TestUtils, and the package's own non-dev modules can't reach them either; that already fails with --prod. I think that's the right semantics. We should report it as a clear build error rather than "module can't be found", and mention it in the changelog, since monorepos that share test helpers through a dev folder will need to move them to a regular source folder.

Would you be up for reworking the PR in this direction? Your shell test is a good base for the regression test. I'd also add the namespace-entry case and a non-dev module that uses a dev module. For 12.3.2, the same change in the Rust rewatch alone should be a small backport. Happy to help, or to take it over if you prefer.

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

2 participants