Skip to content

Commit 5027f68

Browse files
committed
Bug 1496852 - Support JavaScript export-ns-from syntax r=jorendorff
Add support for `export * as ns from "a";` syntax. This was added in ECMAScript 2020. One wrinkle in the implementation is that the parser decides whether to use a lexical vs indirect binding before module linking. Instead, we reserve a hidden environment slot called "*namespace*" for each module that can be referenced by indirect binding maps as needed. Spec is a needs-consensus PR at tc39/ecma262#1174 Depends on D80984 Differential Revision: https://phabricator.services.mozilla.com/D80777
1 parent 1bf7812 commit 5027f68

29 files changed

+142
-34
lines changed

js/src/builtin/Module.js

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ function ModuleResolveExport(exportName, resolveSet = [])
136136
if (exportName === e.exportName) {
137137
let importedModule = CallModuleResolveHook(module, e.moduleRequest,
138138
MODULE_STATUS_UNLINKED);
139-
140-
// TODO: Step 7.a.ii
141-
139+
if (e.importName === "*") {
140+
return {module: importedModule, bindingName: "*namespace*"};
141+
}
142142
return callFunction(importedModule.resolveExport, importedModule, e.importName,
143143
resolveSet);
144144
}
@@ -232,8 +232,18 @@ function ModuleNamespaceCreate(module, exports)
232232
let name = exports[i];
233233
let binding = callFunction(module.resolveExport, module, name);
234234
assert(IsResolvedBinding(binding), "Failed to resolve binding");
235-
// TODO: ES2020 9.4.6.7 Module Namespace Exotic Object [[Get]], Step 10.
236-
AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName);
235+
// ES2020 9.4.6.7 Module Namespace Exotic Object [[Get]], Step 10.
236+
if (binding.bindingName === "*namespace*") {
237+
let namespace = GetModuleNamespace(binding.module);
238+
239+
// The spec uses an immutable binding here but we have already
240+
// generated bytecode for an indirect binding. Instead, use an
241+
// indirect binding to "*namespace*" slot of the target environment.
242+
EnsureModuleEnvironmentNamespace(binding.module, namespace);
243+
AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName);
244+
} else {
245+
AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName);
246+
}
237247
}
238248

239249
return ns;
@@ -477,9 +487,21 @@ function InitializeEnvironment()
477487
imp.lineNumber, imp.columnNumber);
478488
}
479489

480-
// TODO: Step 9.d.iii
481-
482-
CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName);
490+
if (resolution.bindingName === "*namespace*") {
491+
let namespace = GetModuleNamespace(resolution.module);
492+
493+
// This should be CreateNamespaceBinding, but we have already
494+
// generated bytecode assuming an indirect binding. Instead,
495+
// ensure a special "*namespace*"" binding exists on the target
496+
// module's environment. We then generate an indirect binding to
497+
// this synthetic binding.
498+
EnsureModuleEnvironmentNamespace(resolution.module, namespace);
499+
CreateImportBinding(env, imp.localName, resolution.module,
500+
resolution.bindingName);
501+
} else {
502+
CreateImportBinding(env, imp.localName, resolution.module,
503+
resolution.bindingName);
504+
}
483505
}
484506
}
485507

js/src/builtin/ModuleObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,7 @@ bool ModuleBuilder::buildTables(frontend::StencilModuleMetadata& metadata) {
12461246
}
12471247
}
12481248
}
1249-
} else if (exp.importName == cx_->names().star) {
1249+
} else if (exp.importName == cx_->names().star && !exp.exportName) {
12501250
if (!metadata.starExportEntries.append(exp)) {
12511251
return false;
12521252
}

js/src/builtin/TestingFunctions.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5054,6 +5054,10 @@ static bool GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp) {
50545054
return false;
50555055
}
50565056

5057+
// The "*namespace*" binding is a detail of current implementation so hide
5058+
// it to give stable results in tests.
5059+
ids.eraseIfEqual(NameToId(cx->names().starNamespaceStar));
5060+
50575061
uint32_t length = ids.length();
50585062
RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length));
50595063
if (!array) {

js/src/frontend/Parser.cpp

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,17 @@ ModuleNode* Parser<FullParseHandler, Unit>::moduleBody(
16491649
p->value()->setClosedOver();
16501650
}
16511651

1652+
// Reserve an environment slot for a "*namespace*" psuedo-binding and mark as
1653+
// closed-over. We do not know until module linking if this will be used.
1654+
if (!noteDeclaredName(cx_->names().starNamespaceStar, DeclarationKind::Const,
1655+
pos())) {
1656+
return nullptr;
1657+
}
1658+
modulepc.varScope()
1659+
.lookupDeclaredName(cx_->names().starNamespaceStar)
1660+
->value()
1661+
->setClosedOver();
1662+
16521663
if (!CheckParseTree(cx_, alloc_, stmtList)) {
16531664
return null();
16541665
}
@@ -5150,14 +5161,47 @@ GeneralParser<ParseHandler, Unit>::exportBatch(uint32_t begin) {
51505161
return null();
51515162
}
51525163

5153-
// Handle the form |export *| by adding a special export batch
5154-
// specifier to the list.
5155-
NullaryNodeType exportSpec = handler_.newExportBatchSpec(pos());
5156-
if (!exportSpec) {
5164+
bool foundAs;
5165+
if (!tokenStream.matchToken(&foundAs, TokenKind::As)) {
51575166
return null();
51585167
}
51595168

5160-
handler_.addList(kid, exportSpec);
5169+
if (foundAs) {
5170+
if (!mustMatchToken(TokenKindIsPossibleIdentifierName,
5171+
JSMSG_NO_EXPORT_NAME)) {
5172+
return null();
5173+
}
5174+
5175+
NameNodeType exportName = newName(anyChars.currentName());
5176+
if (!exportName) {
5177+
return null();
5178+
}
5179+
5180+
if (!checkExportedNameForClause(exportName)) {
5181+
return null();
5182+
}
5183+
5184+
NameNodeType importName = newName(cx_->names().star);
5185+
if (!importName) {
5186+
return null();
5187+
}
5188+
5189+
BinaryNodeType exportSpec = handler_.newExportSpec(importName, exportName);
5190+
if (!exportSpec) {
5191+
return null();
5192+
}
5193+
5194+
handler_.addList(kid, exportSpec);
5195+
} else {
5196+
// Handle the form |export *| by adding a special export batch
5197+
// specifier to the list.
5198+
NullaryNodeType exportSpec = handler_.newExportBatchSpec(pos());
5199+
if (!exportSpec) {
5200+
return null();
5201+
}
5202+
5203+
handler_.addList(kid, exportSpec);
5204+
}
51615205

51625206
if (!mustMatchToken(TokenKind::From, JSMSG_FROM_AFTER_EXPORT_STAR)) {
51635207
return null();

js/src/jit-test/lib/syntax.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,13 @@ function test_syntax(postfixes, check_error, ignore_opts) {
443443
test("export * from 'a' ", opts);
444444
test("export * from 'a'; ", opts);
445445

446+
test("export * ", opts);
447+
test("export * as ", opts);
448+
test("export * as ns ", opts);
449+
test("export * as ns from ", opts);
450+
test("export * as ns from 'a' ", opts);
451+
test("export * as ns from 'a'; ", opts);
452+
446453
test("export function ", opts);
447454
test("export function f ", opts);
448455
test("export function f( ", opts);

js/src/jit-test/modules/export-ns.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * as ns from "module1.js";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// |jit-test| module
2+
3+
import { ns } from "export-ns.js";
4+
5+
load(libdir + 'asserts.js');
6+
7+
assertEq(isProxy(ns), true);
8+
assertEq(ns.a, 1);
9+
assertThrowsInstanceOf(function() { eval("delete ns"); }, SyntaxError);
10+
assertThrowsInstanceOf(function() { ns = null; }, TypeError);

js/src/tests/test262-update.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
"class-methods-private",
2525
"class-static-methods-private",
2626
"regexp-match-indices",
27-
"export-star-as-namespace-from-module",
2827
"Intl.DateTimeFormat-quarter",
2928
"Intl.Segmenter",
3029
"top-level-await",

js/src/tests/test262/language/expressions/dynamic-import/namespace/await-ns-get-nested-namespace-dflt-direct.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// |reftest| skip async -- export-star-as-namespace-from-module is not supported
1+
// |reftest| async
22
// This file was procedurally generated from the following sources:
33
// - src/dynamic-import/ns-get-nested-namespace-dflt-direct.case
44
// - src/dynamic-import/namespace/await.template

js/src/tests/test262/language/expressions/dynamic-import/namespace/await-ns-get-nested-namespace-dflt-indirect.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// |reftest| skip module async -- export-star-as-namespace-from-module is not supported
1+
// |reftest| module async
22
// This file was procedurally generated from the following sources:
33
// - src/dynamic-import/ns-get-nested-namespace-dflt-indirect.case
44
// - src/dynamic-import/namespace/await.template

0 commit comments

Comments
 (0)