Weird handling of `import * as ns from "..."; export { ns }`
In ParseModule we have some logic to make sure that import { foo } from "mod"; export { foo } is equivalent to export { foo } from "mod"
See Table 59 for how export { foo } from "mod" is represented: ExportEntry Record { [[ExportName]]: "foo", [[ModuleRequest]]: "mod", [[ImportName]]: "foo", [[LocalName]]: null }. Step 10.a.ii.3 makes sure that we build the same ExportEntry Record for import { foo } from "mod"; export { foo }.
For some reason, we are not doing the same in step 10.a.ii.2, which handles import * as foo from "mod"; export { foo }. export * as foo from "mod" is represented as ExportEntry Record { [[ExportName]]: "foo", [[ModuleRequest]]: "mod", [[ImportName]]: ~all~, [[LocalName]]: null }, but in the import+export case we'll keep it as ExportEntry Record { [[ExportName]]: "foo", [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: "foo" }, as if we were exporting a local binding.
I am not sure yet whether this has any observable consequence or not. export * as ns from "mod" was introduced after ES6 (in https://github.com/tc39/ecma262/pull/1174, by @spectranaut), and it seems like this was never brought up.
I'm manly opening this issue as a reminder for myself to check if it matters or not.
Ok I believe an observable difference (but I didn't check yet) might be that this code works:
// entrypoint
import { foo } from "./join.js";
// join.js
export * from "./left.js";
export * from "./right.js";
// left.js
export { foo } from "./mod.js";
// right.js
import { foo } from "./mod.js";
export { foo };
// mod.js
export const foo = 2;
This code works:
// entrypoint
import { foo } from "./join.js";
// join.js
export * from "./left.js";
export * from "./right.js";
// left.js
export * as foo from "./mod.js";
// right.js
export * as foo from "./mod.js";
// mod.js
export const x = 2;
But this throws:
// entrypoint
import { foo } from "./join.js";
// join.js
export * from "./left.js";
export * from "./right.js";
// left.js
export * as foo from "./mod.js";
// right.js
import * as foo from "./mod.js";
export { foo };
// mod.js
export const x = 2;
It seems like browsers disagree about when a namespace binding is ambiguous or not.
I'll present something in plenary, to decide whether we want to:
- keep the spec as-is
- make it always ambiguous
- make neither case ambiguous
All these options will require implementation changes anyway.
This seems like a spec bug to me, we should support the unambiguous case here.
@guybedford by "catch the ambiguous case here" do you mean that the first example in https://github.com/tc39/ecma262/issues/3710#issuecomment-3443845451 should be made to throw, or that the spec should handle this case such that the second example should not throw?
My preferred resolution is definitely for neither case to throw, unless I'm missing something - these are literally the same value being exported, and that can be statically determined, so there's no reason for it to be treated as a conflict.
@bakkot it was clarified to me in the meeting discussion that the ambiguous error is properly thrown for distinct exports, I've updated my comment, based on the same resolution you suggest.
Tried a PR and put it on the agenda as either me or Nicolò presenting.
@nicolo-ribaudo I expect I can manage presenting this if you can't make it or don't have time, but I would be happy to have you present the topic if you're up for it. Alternatively if you think my PR is wrong I'm happy to withdraw it and the agenda item.
Happy to present, thank you!