[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:
Georg Neis 2018-10-10 11:35:07 +02:00 committed by Commit Bot
parent f99329733e
commit 812e768cbe
16 changed files with 205 additions and 13 deletions

View File

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

View File

@ -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") \

View File

@ -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);

View File

@ -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];

View File

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

View 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";

View 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'

View 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);

View 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;

View 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());

View 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);

View 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);

View 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);

View 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";

View 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";

View File

@ -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',