Skip to content

Commit b6b2233

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 d7e084e commit b6b2233

File tree

10 files changed

+124
-17
lines changed

10 files changed

+124
-17
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 = new_List())
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;
@@ -478,9 +488,21 @@ function InitializeEnvironment()
478488
imp.lineNumber, imp.columnNumber);
479489
}
480490

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

js/src/builtin/ModuleObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1300,7 +1300,7 @@ ModuleBuilder::buildTables()
13001300
return false;
13011301
}
13021302
}
1303-
} else if (exp->importName() == cx_->names().star) {
1303+
} else if (exp->importName() == cx_->names().star && !exp->exportName()) {
13041304
if (!starExportEntries_.append(exp))
13051305
return false;
13061306
} else {

js/src/builtin/TestingFunctions.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3876,6 +3876,10 @@ GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp)
38763876
if (!JS_Enumerate(cx, env, &ids))
38773877
return false;
38783878

3879+
// The "*namespace*" binding is a detail of current implementation so hide
3880+
// it to give stable results in tests.
3881+
ids.eraseIfEqual(NameToId(cx->names().starNamespaceStar));
3882+
38793883
uint32_t length = ids.length();
38803884
RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length));
38813885
if (!array)

js/src/frontend/Parser.cpp

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,6 +2333,17 @@ Parser<FullParseHandler, char16_t>::moduleBody(ModuleSharedContext* modulesc)
23332333
p->value()->setClosedOver();
23342334
}
23352335

2336+
// Reserve an environment slot for a "*namespace*" psuedo-binding and mark as
2337+
// closed-over. We do not know until module linking if this will be used.
2338+
if (!noteDeclaredName(context->names().starNamespaceStar, DeclarationKind::Const,
2339+
pos())) {
2340+
return nullptr;
2341+
}
2342+
modulepc.varScope()
2343+
.lookupDeclaredName(context->names().starNamespaceStar)
2344+
->value()
2345+
->setClosedOver();
2346+
23362347
if (!FoldConstants(context, &pn, this))
23372348
return null();
23382349

@@ -5578,13 +5589,44 @@ Parser<ParseHandler, CharT>::exportBatch(uint32_t begin)
55785589
if (!kid)
55795590
return null();
55805591

5581-
// Handle the form |export *| by adding a special export batch
5582-
// specifier to the list.
5583-
Node exportSpec = handler.newExportBatchSpec(pos());
5584-
if (!exportSpec)
5585-
return null();
5592+
bool foundAs;
5593+
if (!tokenStream.matchToken(&foundAs, TokenKind::As)) {
5594+
return null();
5595+
}
5596+
5597+
if (foundAs) {
5598+
MUST_MATCH_TOKEN_FUNC(TokenKindIsPossibleIdentifierName, JSMSG_NO_EXPORT_NAME);
5599+
5600+
Node exportName = newName(anyChars.currentName());
5601+
if (!exportName) {
5602+
return null();
5603+
}
5604+
5605+
if (!checkExportedNameForClause(exportName)) {
5606+
return null();
5607+
}
55865608

5587-
handler.addList(kid, exportSpec);
5609+
Node importName = newName(context->names().star);
5610+
if (!importName) {
5611+
return null();
5612+
}
5613+
5614+
Node exportSpec = handler.newExportSpec(importName, exportName);
5615+
if (!exportSpec) {
5616+
return null();
5617+
}
5618+
5619+
handler.addList(kid, exportSpec);
5620+
} else {
5621+
// Handle the form |export *| by adding a special export batch
5622+
// specifier to the list.
5623+
Node exportSpec = handler.newExportBatchSpec(pos());
5624+
if (!exportSpec) {
5625+
return null();
5626+
}
5627+
5628+
handler.addList(kid, exportSpec);
5629+
}
55885630

55895631
MUST_MATCH_TOKEN(TokenKind::From, JSMSG_FROM_AFTER_EXPORT_STAR);
55905632

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

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

454+
test("export * ", opts);
455+
test("export * as ", opts);
456+
test("export * as ns ", opts);
457+
test("export * as ns from ", opts);
458+
test("export * as ns from 'a' ", opts);
459+
test("export * as ns from 'a'; ", opts);
460+
454461
test("export function ", opts);
455462
test("export function f ", opts);
456463
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/vm/CommonPropertyNames.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@
380380
macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \
381381
macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \
382382
macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \
383+
macro(starNamespaceStar, starNamespaceStar, "*namespace*") \
383384
macro(start, start, "start") \
384385
macro(startTimestamp, startTimestamp, "startTimestamp") \
385386
macro(state, state, "state") \

js/src/vm/EnvironmentObject.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,11 +736,12 @@ static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) {
736736
#ifdef DEBUG
737737
static bool IsInternalDotName(JSContext* cx, HandleId id) {
738738
return JSID_IS_ATOM(id, cx->names().dotThis) ||
739-
JSID_IS_ATOM(id, cx->names().dotGenerator); /* || The following aren't currently implemented in Waterfox
739+
JSID_IS_ATOM(id, cx->names().dotGenerator) /* || The following aren't currently implemented in Waterfox
740740
JSID_IS_ATOM(id, cx->names().dotInitializers) ||
741741
JSID_IS_ATOM(id, cx->names().dotFieldKeys) ||
742742
JSID_IS_ATOM(id, cx->names().dotStaticInitializers) ||
743-
JSID_IS_ATOM(id, cx->names().dotStaticFieldKeys); */
743+
JSID_IS_ATOM(id, cx->names().dotStaticFieldKeys) */ ||
744+
JSID_IS_ATOM(id, cx->names().starNamespaceStar);
744745
}
745746
#endif
746747

js/src/vm/SelfHosting.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,6 +2166,23 @@ intrinsic_CreateNamespaceBinding(JSContext* cx, unsigned argc, Value* vp)
21662166
return true;
21672167
}
21682168

2169+
static bool intrinsic_EnsureModuleEnvironmentNamespace(JSContext* cx,
2170+
unsigned argc,
2171+
Value* vp) {
2172+
CallArgs args = CallArgsFromVp(argc, vp);
2173+
MOZ_ASSERT(args.length() == 2);
2174+
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
2175+
MOZ_ASSERT(args[1].toObject().is<ModuleNamespaceObject>());
2176+
RootedModuleEnvironmentObject environment(cx, &module->initialEnvironment());
2177+
// The property already exists in the evironment but is not writable, so set
2178+
// the slot directly.
2179+
RootedShape shape(cx, environment->lookup(cx, cx->names().starNamespaceStar));
2180+
MOZ_ASSERT(shape);
2181+
environment->setSlot(shape->slot(), args[1]);
2182+
args.rval().setUndefined();
2183+
return true;
2184+
}
2185+
21692186
static bool
21702187
intrinsic_InstantiateModuleFunctionDeclarations(JSContext* cx, unsigned argc, Value* vp)
21712188
{
@@ -2679,6 +2696,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
26792696
JS_FN("IsModuleEnvironment", intrinsic_IsInstanceOfBuiltin<ModuleEnvironmentObject>, 1, 0),
26802697
JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0),
26812698
JS_FN("CreateNamespaceBinding", intrinsic_CreateNamespaceBinding, 3, 0),
2699+
JS_FN("EnsureModuleEnvironmentNamespace",
2700+
intrinsic_EnsureModuleEnvironmentNamespace, 1, 0),
26822701
JS_FN("InstantiateModuleFunctionDeclarations",
26832702
intrinsic_InstantiateModuleFunctionDeclarations, 1, 0),
26842703
JS_FN("ExecuteModule", intrinsic_ExecuteModule, 1, 0),

0 commit comments

Comments
 (0)