[modules] Implement new syntax: export * as foo from "..."
This is behind a new flag --harmony-namespace-exports. Bug: v8:8101 Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng Change-Id: I9c252b6de2b08223fcf3296340b78d721471bdb4 Reviewed-on: https://chromium-review.googlesource.com/c/1258004 Commit-Queue: Georg Neis <neis@chromium.org> Reviewed-by: Adam Klein <adamk@chromium.org> Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Cr-Commit-Position: refs/heads/master@{#56550}
This commit is contained in:
parent
f99329733e
commit
812e768cbe
@ -4381,6 +4381,7 @@ void Bootstrapper::ExportFromRuntime(Isolate* isolate,
|
||||
void Genesis::InitializeGlobal_##id() {}
|
||||
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_do_expressions)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_namespace_exports)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_public_fields)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_private_fields)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_static_fields)
|
||||
|
@ -228,6 +228,8 @@ DEFINE_IMPLICATION(harmony_class_fields, harmony_private_fields)
|
||||
|
||||
// Features that are complete (but still behind --harmony/es-staging flag).
|
||||
#define HARMONY_STAGED(V) \
|
||||
V(harmony_namespace_exports, \
|
||||
"harmony namespace exports (export * as foo from 'bar')") \
|
||||
V(harmony_public_fields, "harmony public fields in class literals") \
|
||||
V(harmony_private_fields, "harmony private fields in class literals") \
|
||||
V(harmony_numeric_separator, "harmony numeric separator between digits") \
|
||||
|
@ -1168,17 +1168,64 @@ Statement* Parser::ParseExportDefault(bool* ok) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const AstRawString* Parser::NextInternalNamespaceExportName() {
|
||||
const char* prefix = ".ns-export";
|
||||
std::string s(prefix);
|
||||
s.append(std::to_string(number_of_named_namespace_exports_++));
|
||||
return ast_value_factory()->GetOneByteString(s.c_str());
|
||||
}
|
||||
|
||||
void Parser::ParseExportStar(bool* ok) {
|
||||
int pos = position();
|
||||
Consume(Token::MUL);
|
||||
|
||||
if (!FLAG_harmony_namespace_exports || !PeekContextualKeyword(Token::AS)) {
|
||||
// 'export' '*' 'from' ModuleSpecifier ';'
|
||||
Scanner::Location loc = scanner()->location();
|
||||
ExpectContextualKeyword(Token::FROM, CHECK_OK_VOID);
|
||||
Scanner::Location specifier_loc = scanner()->peek_location();
|
||||
const AstRawString* module_specifier = ParseModuleSpecifier(CHECK_OK_VOID);
|
||||
ExpectSemicolon(CHECK_OK_VOID);
|
||||
module()->AddStarExport(module_specifier, loc, specifier_loc, zone());
|
||||
return;
|
||||
}
|
||||
if (!FLAG_harmony_namespace_exports) return;
|
||||
|
||||
// 'export' '*' 'as' IdentifierName 'from' ModuleSpecifier ';'
|
||||
//
|
||||
// Desugaring:
|
||||
// export * as x from "...";
|
||||
// ~>
|
||||
// import * as .x from "..."; export {.x as x};
|
||||
|
||||
ExpectContextualKeyword(Token::AS, CHECK_OK_VOID);
|
||||
const AstRawString* export_name = ParseIdentifierName(CHECK_OK_VOID);
|
||||
Scanner::Location export_name_loc = scanner()->location();
|
||||
const AstRawString* local_name = NextInternalNamespaceExportName();
|
||||
Scanner::Location local_name_loc = Scanner::Location::invalid();
|
||||
DeclareVariable(local_name, VariableMode::kConst, kCreatedInitialized, pos,
|
||||
CHECK_OK_VOID);
|
||||
|
||||
ExpectContextualKeyword(Token::FROM, CHECK_OK_VOID);
|
||||
Scanner::Location specifier_loc = scanner()->peek_location();
|
||||
const AstRawString* module_specifier = ParseModuleSpecifier(CHECK_OK_VOID);
|
||||
ExpectSemicolon(CHECK_OK_VOID);
|
||||
|
||||
module()->AddStarImport(local_name, module_specifier, local_name_loc,
|
||||
specifier_loc, zone());
|
||||
module()->AddExport(local_name, export_name, export_name_loc, zone());
|
||||
}
|
||||
|
||||
Statement* Parser::ParseExportDeclaration(bool* ok) {
|
||||
// ExportDeclaration:
|
||||
// 'export' '*' 'from' ModuleSpecifier ';'
|
||||
// 'export' '*' 'as' IdentifierName 'from' ModuleSpecifier ';'
|
||||
// 'export' ExportClause ('from' ModuleSpecifier)? ';'
|
||||
// 'export' VariableStatement
|
||||
// 'export' Declaration
|
||||
// 'export' 'default' ... (handled in ParseExportDefault)
|
||||
|
||||
Expect(Token::EXPORT, CHECK_OK);
|
||||
int pos = position();
|
||||
|
||||
Statement* result = nullptr;
|
||||
ZonePtrList<const AstRawString> names(1, zone());
|
||||
Scanner::Location loc = scanner()->peek_location();
|
||||
@ -1186,16 +1233,9 @@ Statement* Parser::ParseExportDeclaration(bool* ok) {
|
||||
case Token::DEFAULT:
|
||||
return ParseExportDefault(ok);
|
||||
|
||||
case Token::MUL: {
|
||||
Consume(Token::MUL);
|
||||
loc = scanner()->location();
|
||||
ExpectContextualKeyword(Token::FROM, CHECK_OK);
|
||||
Scanner::Location specifier_loc = scanner()->peek_location();
|
||||
const AstRawString* module_specifier = ParseModuleSpecifier(CHECK_OK);
|
||||
ExpectSemicolon(CHECK_OK);
|
||||
module()->AddStarExport(module_specifier, loc, specifier_loc, zone());
|
||||
return factory()->NewEmptyStatement(pos);
|
||||
}
|
||||
case Token::MUL:
|
||||
ParseExportStar(CHECK_OK);
|
||||
return factory()->NewEmptyStatement(position());
|
||||
|
||||
case Token::LBRACE: {
|
||||
// There are two cases here:
|
||||
@ -1209,6 +1249,7 @@ Statement* Parser::ParseExportDeclaration(bool* ok) {
|
||||
// pass in a location that gets filled with the first reserved word
|
||||
// encountered, and then throw a SyntaxError if we are in the
|
||||
// non-FromClause case.
|
||||
int pos = position();
|
||||
Scanner::Location reserved_loc = Scanner::Location::invalid();
|
||||
ZoneChunkList<ExportClauseData>* export_data =
|
||||
ParseExportClause(&reserved_loc, CHECK_OK);
|
||||
|
@ -269,6 +269,7 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
|
||||
void ParseImportDeclaration(bool* ok);
|
||||
Statement* ParseExportDeclaration(bool* ok);
|
||||
Statement* ParseExportDefault(bool* ok);
|
||||
void ParseExportStar(bool* ok);
|
||||
struct ExportClauseData {
|
||||
const AstRawString* export_name;
|
||||
const AstRawString* local_name;
|
||||
@ -1096,6 +1097,10 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
|
||||
node, new (zone()) TryFinallyStatementSourceRanges(body_range));
|
||||
}
|
||||
|
||||
// Generate the next internal variable name for binding an exported namespace
|
||||
// object (used to implement the "export * as" syntax).
|
||||
const AstRawString* NextInternalNamespaceExportName();
|
||||
|
||||
// Parser's private field members.
|
||||
friend class PreParserZoneScope; // Uses reusable_preparser().
|
||||
|
||||
@ -1112,6 +1117,9 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
|
||||
|
||||
ScriptCompiler::CompileOptions compile_options_;
|
||||
|
||||
// For NextInternalNamespaceExportName().
|
||||
int number_of_named_namespace_exports_ = 0;
|
||||
|
||||
// Other information which will be stored in Parser and moved to Isolate after
|
||||
// parsing.
|
||||
int use_counts_[v8::Isolate::kUseCounterFeatureCount];
|
||||
|
@ -6574,6 +6574,41 @@ TEST(BasicImportExportParsing) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NamespaceExportParsing) {
|
||||
// clang-format off
|
||||
const char* kSources[] = {
|
||||
"export * as arguments from 'bar'",
|
||||
"export * as await from 'bar'",
|
||||
"export * as default from 'bar'",
|
||||
"export * as enum from 'bar'",
|
||||
"export * as foo from 'bar'",
|
||||
"export * as for from 'bar'",
|
||||
"export * as let from 'bar'",
|
||||
"export * as static from 'bar'",
|
||||
"export * as yield from 'bar'",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
i::FLAG_harmony_namespace_exports = true;
|
||||
i::Isolate* isolate = CcTest::i_isolate();
|
||||
i::Factory* factory = isolate->factory();
|
||||
|
||||
v8::HandleScope handles(CcTest::isolate());
|
||||
v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() -
|
||||
128 * 1024);
|
||||
|
||||
for (unsigned i = 0; i < arraysize(kSources); ++i) {
|
||||
i::Handle<i::String> source =
|
||||
factory->NewStringFromAsciiChecked(kSources[i]);
|
||||
i::Handle<i::Script> script = factory->NewScript(source);
|
||||
i::ParseInfo info(isolate, script);
|
||||
info.set_module();
|
||||
CHECK(i::parsing::ParseProgram(&info, isolate));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ImportExportParsingErrors) {
|
||||
// clang-format off
|
||||
@ -6639,6 +6674,13 @@ TEST(ImportExportParsingErrors) {
|
||||
"import * as x, * as y from 'm.js';",
|
||||
"import {x}, {y} from 'm.js';",
|
||||
"import * as x, {y} from 'm.js';",
|
||||
|
||||
"export *;",
|
||||
"export * as;",
|
||||
"export * as foo;",
|
||||
"export * as foo from;",
|
||||
"export * as foo from ';",
|
||||
"export * as ,foo from 'bar'",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
9
test/message/fail/modules-duplicate-export5.js
Normal file
9
test/message/fail/modules-duplicate-export5.js
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// MODULE
|
||||
// Flags: --harmony-namespace-exports
|
||||
|
||||
export let foo = 42;
|
||||
export * as foo from "./doesnt-even-matter.js";
|
5
test/message/fail/modules-duplicate-export5.out
Normal file
5
test/message/fail/modules-duplicate-export5.out
Normal file
@ -0,0 +1,5 @@
|
||||
*%(basename)s:9: SyntaxError: Duplicate export of 'foo'
|
||||
export * as foo from "./doesnt-even-matter.js";
|
||||
^^^
|
||||
SyntaxError: Duplicate export of 'foo'
|
||||
|
11
test/mjsunit/harmony/modules-import-17.js
Normal file
11
test/mjsunit/harmony/modules-import-17.js
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --allow-natives-syntax --harmony-namespace-exports
|
||||
|
||||
var ns;
|
||||
import('modules-skip-13.js').then(x => ns = x);
|
||||
%RunMicrotasks();
|
||||
assertEquals(42, ns.default);
|
||||
assertEquals(ns, ns.self);
|
6
test/mjsunit/harmony/modules-skip-13.js
Normal file
6
test/mjsunit/harmony/modules-skip-13.js
Normal file
@ -0,0 +1,6 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
export * as self from "./modules-skip-13.js";
|
||||
export default 42;
|
10
test/mjsunit/modules-export-star-as1.js
Normal file
10
test/mjsunit/modules-export-star-as1.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// MODULE
|
||||
// Flags: --harmony-namespace-exports
|
||||
|
||||
import {foo} from "./modules-skip-8.js";
|
||||
assertEquals(42, foo.default);
|
||||
assertEquals(1, foo.get_a());
|
19
test/mjsunit/modules-export-star-as2.js
Normal file
19
test/mjsunit/modules-export-star-as2.js
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// MODULE
|
||||
// Flags: --harmony-namespace-exports
|
||||
|
||||
export * as self from "./modules-export-star-as2.js";
|
||||
export * as self_again from "./modules-export-star-as2.js";
|
||||
import {self as myself} from "./modules-export-star-as2.js";
|
||||
import {self_again as myself_again} from "./modules-export-star-as2.js";
|
||||
|
||||
assertEquals(["self", "self_again"], Object.keys(myself));
|
||||
assertEquals(myself, myself.self);
|
||||
assertEquals(myself_again, myself.self_again);
|
||||
assertEquals(myself, myself_again);
|
||||
|
||||
assertThrows(_ => self, ReferenceError);
|
||||
assertThrows(_ => self_again, ReferenceError);
|
15
test/mjsunit/modules-export-star-as3.js
Normal file
15
test/mjsunit/modules-export-star-as3.js
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// MODULE
|
||||
// Flags: --harmony-namespace-exports
|
||||
|
||||
let self = 42;
|
||||
export * as self from "./modules-export-star-as3.js";
|
||||
import {self as myself} from "./modules-export-star-as3.js";
|
||||
assertEquals(["self"], Object.keys(myself));
|
||||
assertEquals(myself, myself.self);
|
||||
assertEquals(42, self);
|
||||
self++;
|
||||
assertEquals(43, self);
|
11
test/mjsunit/modules-imports8.js
Normal file
11
test/mjsunit/modules-imports8.js
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// MODULE
|
||||
// Flags: --harmony-namespace-exports
|
||||
|
||||
import {a, b} from "./modules-skip-9.js";
|
||||
assertSame(a, b);
|
||||
assertEquals(42, a.default);
|
||||
assertEquals(1, a.a);
|
5
test/mjsunit/modules-skip-8.js
Normal file
5
test/mjsunit/modules-skip-8.js
Normal file
@ -0,0 +1,5 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
export * as foo from "./modules-skip-1.js";
|
7
test/mjsunit/modules-skip-9.js
Normal file
7
test/mjsunit/modules-skip-9.js
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import * as b from "./modules-skip-1.js";
|
||||
export {b};
|
||||
export * as a from "./modules-skip-1.js";
|
@ -56,10 +56,10 @@ FEATURE_FLAGS = {
|
||||
'Symbol.prototype.description': '--harmony-symbol-description',
|
||||
'globalThis': '--harmony-global',
|
||||
'well-formed-json-stringify': '--harmony-json-stringify',
|
||||
'export-star-as-namespace-from-module': '--harmony-namespace-exports',
|
||||
}
|
||||
|
||||
SKIPPED_FEATURES = set(['Object.fromEntries',
|
||||
'export-star-as-namespace-from-module',
|
||||
'class-fields-private',
|
||||
'class-static-fields-private',
|
||||
'class-methods-private',
|
||||
|
Loading…
Reference in New Issue
Block a user