Semantic Diff

#8982 Reject bindings of a module that get the same JavaScript name

1 commit

9e2f371482f529406d70df0565784aa8df3554e9..pr-8982

a781efbe7 · Christoph Knittel with Claude Opus 5.5 · Oct 23, 2026

Reject bindings of a module that get the same JavaScript name

Why

Ext_ident.convert maps both delete and $$delete to $$delete, and \"a-b" like a$b, so two bindings could share an export name or a module object key: the ES export list repeated a name, which Node rejects, and CommonJS output and nested modules lost a binding. The type checker now rejects such a module, at the second binding. This replaces Lam_coercion's "is exported twice" check, which only covered equal source names and had no location.

addedDiagnostic JavaScript name clash

let delete = 1let $$delete = 2
js
Before
let $$delete = 1;let $$delete$1 = 2;export {  $$delete,  $$delete$1 as $$delete,}
diagnostic
After
This value has the same JavaScript name $$deleteas the value at A.res:1:5-10.Rename one of them.

modifiedDiagnostic exception and module of one name

exception M(int)module M = {  let x = 3}
diagnostic
Before
After
M is exported twice This module has the same JavaScript name Mas the extension constructor at A.res:1:1-16.Rename one of them.

Review

  • Why not rename one of the two exports? Importers derive an export's name from the source name alone (js_export_name), so a renamed export would not be found by them; the clash has to be resolved in the source.
  • Which signatures are checked? Each structure's signature and each written signature (module types and .resi files), since every module block is built from one of them. The exports of a compilation unit keep default as written, so only nested modules reject default beside $$default.