ecma262 icon indicating copy to clipboard operation
ecma262 copied to clipboard

Weird handling of `import * as ns from "..."; export { ns }`

Open nicolo-ribaudo opened this issue 1 month ago • 7 comments

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.

nicolo-ribaudo avatar Oct 24 '25 15:10 nicolo-ribaudo

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;

nicolo-ribaudo avatar Oct 24 '25 15:10 nicolo-ribaudo

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.

nicolo-ribaudo avatar Oct 28 '25 10:10 nicolo-ribaudo

This seems like a spec bug to me, we should support the unambiguous case here.

guybedford avatar Nov 06 '25 17:11 guybedford

@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 avatar Nov 06 '25 19:11 bakkot

@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.

guybedford avatar Nov 07 '25 04:11 guybedford

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.

bakkot avatar Nov 08 '25 00:11 bakkot

Happy to present, thank you!

nicolo-ribaudo avatar Nov 08 '25 06:11 nicolo-ribaudo