[ESnext] Parse dynamic import expression

Rewrites import expression into a runtime call. Uses peekahead to
determine if parsing an import declaration or import expression.

The runtime call doesn't actually do the import yet, will be added in
follow on patch.

Adds a new --harmony-dynamic-import flag.

Adds a ignore_error_msg parameter to the test runner to ignore the
discrepancy in the error messages while parsing import expression with
parser and pre parser. This discrepancy will actually never happen in
real code.

BUG=v8:5785

Review-Url: https://codereview.chromium.org/2661933003
Cr-Commit-Position: refs/heads/master@{#42820}
This commit is contained in:
gsathya 2017-01-31 10:58:53 -08:00 committed by Commit bot
parent c41f5f2de6
commit e791ded4cd
8 changed files with 231 additions and 48 deletions

View File

@ -3516,6 +3516,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_restrictive_generators)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_trailing_commas)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_class_fields)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_rest_spread)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import)
void InstallPublicSymbol(Factory* factory, Handle<Context> native_context,
const char* name, Handle<Symbol> value) {
@ -4085,6 +4086,7 @@ bool Genesis::InstallExperimentalNatives() {
static const char* harmony_class_fields_natives[] = {nullptr};
static const char* harmony_object_rest_spread_natives[] = {nullptr};
static const char* harmony_async_iteration_natives[] = {nullptr};
static const char* harmony_dynamic_import_natives[] = {nullptr};
for (int i = ExperimentalNatives::GetDebuggerCount();
i < ExperimentalNatives::GetBuiltinsCount(); i++) {

View File

@ -194,16 +194,17 @@ DEFINE_IMPLICATION(es_staging, harmony_regexp_lookbehind)
DEFINE_IMPLICATION(es_staging, move_object_start)
// Features that are still work in progress (behind individual flags).
#define HARMONY_INPROGRESS(V) \
V(harmony_array_prototype_values, "harmony Array.prototype.values") \
V(harmony_function_sent, "harmony function.sent") \
V(harmony_sharedarraybuffer, "harmony sharedarraybuffer") \
V(harmony_simd, "harmony simd") \
V(harmony_do_expressions, "harmony do-expressions") \
V(harmony_regexp_named_captures, "harmony regexp named captures") \
V(harmony_regexp_property, "harmony unicode regexp property classes") \
V(harmony_class_fields, "harmony public fields in class literals") \
V(harmony_async_iteration, "harmony async iteration")
#define HARMONY_INPROGRESS(V) \
V(harmony_array_prototype_values, "harmony Array.prototype.values") \
V(harmony_function_sent, "harmony function.sent") \
V(harmony_sharedarraybuffer, "harmony sharedarraybuffer") \
V(harmony_simd, "harmony simd") \
V(harmony_do_expressions, "harmony do-expressions") \
V(harmony_regexp_named_captures, "harmony regexp named captures") \
V(harmony_regexp_property, "harmony unicode regexp property classes") \
V(harmony_class_fields, "harmony public fields in class literals") \
V(harmony_async_iteration, "harmony async iteration") \
V(harmony_dynamic_import, "harmony dynamic import")
// Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED(V) \

View File

@ -224,7 +224,8 @@ class ParserBase {
allow_harmony_restrictive_generators_(false),
allow_harmony_trailing_commas_(false),
allow_harmony_class_fields_(false),
allow_harmony_object_rest_spread_(false) {}
allow_harmony_object_rest_spread_(false),
allow_harmony_dynamic_import_(false) {}
#define ALLOW_ACCESSORS(name) \
bool allow_##name() const { return allow_##name##_; } \
@ -238,6 +239,7 @@ class ParserBase {
ALLOW_ACCESSORS(harmony_trailing_commas);
ALLOW_ACCESSORS(harmony_class_fields);
ALLOW_ACCESSORS(harmony_object_rest_spread);
ALLOW_ACCESSORS(harmony_dynamic_import);
#undef ALLOW_ACCESSORS
@ -1206,6 +1208,7 @@ class ParserBase {
int class_token_pos, bool* ok);
ExpressionT ParseTemplateLiteral(ExpressionT tag, int start, bool* ok);
ExpressionT ParseSuperExpression(bool is_new, bool* ok);
ExpressionT ParseDynamicImportExpression(bool* ok);
ExpressionT ParseNewTargetExpression(bool* ok);
void ParseFormalParameter(FormalParametersT* parameters, bool* ok);
@ -1484,6 +1487,7 @@ class ParserBase {
bool allow_harmony_trailing_commas_;
bool allow_harmony_class_fields_;
bool allow_harmony_object_rest_spread_;
bool allow_harmony_dynamic_import_;
friend class DiscardableZoneScope;
};
@ -3350,7 +3354,11 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseMemberExpression(
// MemberExpression ::
// (PrimaryExpression | FunctionLiteral | ClassLiteral)
// ('[' Expression ']' | '.' Identifier | Arguments | TemplateLiteral)*
//
// CallExpression ::
// (SuperCall | ImportCall)
// ('[' Expression ']' | '.' Identifier | Arguments | TemplateLiteral)*
//
// The '[' Expression ']' and '.' Identifier parts are parsed by
// ParseMemberExpressionContinuation, and the Arguments part is parsed by the
// caller.
@ -3403,6 +3411,8 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseMemberExpression(
} else if (peek() == Token::SUPER) {
const bool is_new = false;
result = ParseSuperExpression(is_new, CHECK_OK);
} else if (allow_harmony_dynamic_import() && peek() == Token::IMPORT) {
result = ParseDynamicImportExpression(CHECK_OK);
} else {
result = ParsePrimaryExpression(is_async, CHECK_OK);
}
@ -3411,6 +3421,20 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseMemberExpression(
return result;
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseDynamicImportExpression(bool* ok) {
DCHECK(allow_harmony_dynamic_import());
Consume(Token::IMPORT);
int pos = position();
Expect(Token::LPAREN, CHECK_OK);
ExpressionT arg = ParseAssignmentExpression(true, CHECK_OK);
Expect(Token::RPAREN, CHECK_OK);
ZoneList<ExpressionT>* args = new (zone()) ZoneList<ExpressionT>(1, zone());
args->Add(arg, zone());
return factory()->NewCallRuntime(Runtime::kDynamicImportCall, args, pos);
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseSuperExpression(
bool is_new, bool* ok) {

View File

@ -553,6 +553,7 @@ Parser::Parser(ParseInfo* info)
set_allow_harmony_trailing_commas(FLAG_harmony_trailing_commas);
set_allow_harmony_class_fields(FLAG_harmony_class_fields);
set_allow_harmony_object_rest_spread(FLAG_harmony_object_rest_spread);
set_allow_harmony_dynamic_import(FLAG_harmony_dynamic_import);
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
++feature) {
use_counts_[feature] = 0;
@ -962,15 +963,21 @@ Statement* Parser::ParseModuleItem(bool* ok) {
// ExportDeclaration
// StatementListItem
switch (peek()) {
case Token::IMPORT:
ParseImportDeclaration(CHECK_OK);
return factory()->NewEmptyStatement(kNoSourcePosition);
case Token::EXPORT:
return ParseExportDeclaration(ok);
default:
return ParseStatementListItem(ok);
Token::Value next = peek();
if (next == Token::EXPORT) {
return ParseExportDeclaration(ok);
}
// We must be careful not to parse a dynamic import expression as an import
// declaration.
if (next == Token::IMPORT &&
(!allow_harmony_dynamic_import() || PeekAhead() != Token::LPAREN)) {
ParseImportDeclaration(CHECK_OK);
return factory()->NewEmptyStatement(kNoSourcePosition);
}
return ParseStatementListItem(ok);
}
@ -2795,6 +2802,7 @@ Parser::LazyParsingResult Parser::SkipFunction(
SET_ALLOW(harmony_trailing_commas);
SET_ALLOW(harmony_class_fields);
SET_ALLOW(harmony_object_rest_spread);
SET_ALLOW(harmony_dynamic_import);
#undef SET_ALLOW
}
// Aborting inner function preparsing would leave scopes in an inconsistent

View File

@ -759,6 +759,12 @@ class PreParserFactory {
return PreParserStatement::Default();
}
PreParserExpression NewCallRuntime(Runtime::FunctionId id,
ZoneList<PreParserExpression>* arguments,
int pos) {
return PreParserExpression::Default();
}
// Return the object itself as AstVisitor and implement the needed
// dummy method right in this class.
PreParserFactory* visitor() { return this; }

View File

@ -9,6 +9,13 @@
namespace v8 {
namespace internal {
RUNTIME_FUNCTION(Runtime_DynamicImportCall) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
// TODO(gsathya): Implement ImportCall.
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_GetModuleNamespace) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());

View File

@ -350,6 +350,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_MATHS(F) F(GenerateRandomNumbers, 0, 1)
#define FOR_EACH_INTRINSIC_MODULE(F) \
F(DynamicImportCall, 1, 1) \
F(GetModuleNamespace, 1, 1) \
F(LoadModuleVariable, 1, 1) \
F(StoreModuleVariable, 2, 1)

View File

@ -1285,6 +1285,7 @@ enum ParserFlag {
kAllowHarmonyTrailingCommas,
kAllowHarmonyClassFields,
kAllowHarmonyObjectRestSpread,
kAllowHarmonyDynamicImport,
};
enum ParserSyncTestResult {
@ -1302,6 +1303,7 @@ void SetGlobalFlags(i::EnumSet<ParserFlag> flags) {
i::FLAG_harmony_class_fields = flags.Contains(kAllowHarmonyClassFields);
i::FLAG_harmony_object_rest_spread =
flags.Contains(kAllowHarmonyObjectRestSpread);
i::FLAG_harmony_dynamic_import = flags.Contains(kAllowHarmonyDynamicImport);
}
void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) {
@ -1316,13 +1318,15 @@ void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) {
flags.Contains(kAllowHarmonyClassFields));
parser->set_allow_harmony_object_rest_spread(
flags.Contains(kAllowHarmonyObjectRestSpread));
parser->set_allow_harmony_dynamic_import(
flags.Contains(kAllowHarmonyDynamicImport));
}
void TestParserSyncWithFlags(i::Handle<i::String> source,
i::EnumSet<ParserFlag> flags,
ParserSyncTestResult result,
bool is_module = false,
bool test_preparser = true) {
bool is_module = false, bool test_preparser = true,
bool ignore_error_msg = false) {
i::Isolate* isolate = CcTest::i_isolate();
i::Factory* factory = isolate->factory();
@ -1399,7 +1403,7 @@ void TestParserSyncWithFlags(i::Handle<i::String> source,
CHECK(false);
}
// Check that preparser and parser produce the same error.
if (test_preparser) {
if (test_preparser && !ignore_error_msg) {
i::Handle<i::String> preparser_message =
pending_error_handler.FormatMessage(CcTest::i_isolate());
if (!i::String::Equals(message_string, preparser_message)) {
@ -1446,7 +1450,6 @@ void TestParserSyncWithFlags(i::Handle<i::String> source,
}
}
void TestParserSync(const char* source, const ParserFlag* varying_flags,
size_t varying_flags_length,
ParserSyncTestResult result = kSuccessOrError,
@ -1454,7 +1457,8 @@ void TestParserSync(const char* source, const ParserFlag* varying_flags,
size_t always_true_flags_length = 0,
const ParserFlag* always_false_flags = NULL,
size_t always_false_flags_length = 0,
bool is_module = false, bool test_preparser = true) {
bool is_module = false, bool test_preparser = true,
bool ignore_error_msg = false) {
i::Handle<i::String> str =
CcTest::i_isolate()->factory()->NewStringFromAsciiChecked(source);
for (int bits = 0; bits < (1 << varying_flags_length); bits++) {
@ -1471,7 +1475,8 @@ void TestParserSync(const char* source, const ParserFlag* varying_flags,
++flag_index) {
flags.Remove(always_false_flags[flag_index]);
}
TestParserSyncWithFlags(str, flags, result, is_module, test_preparser);
TestParserSyncWithFlags(str, flags, result, is_module, test_preparser,
ignore_error_msg);
}
}
@ -1611,16 +1616,13 @@ TEST(StrictOctal) {
*exception));
}
void RunParserSyncTest(const char* context_data[][2],
const char* statement_data[],
ParserSyncTestResult result,
const ParserFlag* flags = NULL, int flags_len = 0,
const ParserFlag* always_true_flags = NULL,
int always_true_len = 0,
const ParserFlag* always_false_flags = NULL,
int always_false_len = 0, bool is_module = false,
bool test_preparser = true) {
void RunParserSyncTest(
const char* context_data[][2], const char* statement_data[],
ParserSyncTestResult result, const ParserFlag* flags = NULL,
int flags_len = 0, const ParserFlag* always_true_flags = NULL,
int always_true_len = 0, const ParserFlag* always_false_flags = NULL,
int always_false_len = 0, bool is_module = false,
bool test_preparser = true, bool ignore_error_msg = false) {
v8::HandleScope handles(CcTest::isolate());
v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
v8::Context::Scope context_scope(context);
@ -1675,25 +1677,23 @@ void RunParserSyncTest(const char* context_data[][2],
CHECK(length == kProgramSize);
TestParserSync(program.start(), flags, flags_len, result,
always_true_flags, always_true_len, always_false_flags,
always_false_len, is_module, test_preparser);
always_false_len, is_module, test_preparser,
ignore_error_msg);
}
}
delete[] generated_flags;
}
void RunModuleParserSyncTest(const char* context_data[][2],
const char* statement_data[],
ParserSyncTestResult result,
const ParserFlag* flags = NULL, int flags_len = 0,
const ParserFlag* always_true_flags = NULL,
int always_true_len = 0,
const ParserFlag* always_false_flags = NULL,
int always_false_len = 0,
bool test_preparser = true) {
void RunModuleParserSyncTest(
const char* context_data[][2], const char* statement_data[],
ParserSyncTestResult result, const ParserFlag* flags = NULL,
int flags_len = 0, const ParserFlag* always_true_flags = NULL,
int always_true_len = 0, const ParserFlag* always_false_flags = NULL,
int always_false_len = 0, bool test_preparser = true,
bool ignore_error_msg = false) {
RunParserSyncTest(context_data, statement_data, result, flags, flags_len,
always_true_flags, always_true_len, always_false_flags,
always_false_len, true, test_preparser);
always_false_len, true, test_preparser, ignore_error_msg);
}
@ -4145,6 +4145,140 @@ TEST(SuperErrors) {
RunParserSyncTest(context_data, expression_data, kError);
}
TEST(ImportExpressionSuccess) {
// clang-format off
const char* context_data[][2] = {
{"", ""},
{NULL, NULL}
};
const char* data[] = {
"new import(x)",
"import(1)",
"import(y=x)",
"f(...[import(y=x)])",
"x = {[import(y=x)]: 1}",
"var {[import(y=x)]: x} = {}",
"({[import(y=x)]: x} = {})",
"async () => { await import(x) }",
"() => { import(x) }",
"(import(y=x))",
"{import(y=x)}",
"import(import(x))",
"x = import(x)",
"var x = import(x)",
"let x = import(x)",
"for(x of import(x)) {}",
"import(x).then()",
NULL
};
// clang-format on
// We ignore test error messages because the error message from the
// parser/preparser is different for the same data depending on the
// context.
// For example, a top level "import(" is parsed as an
// import declaration. The parser parses the import token correctly
// and then shows an "Unexpected token (" error message. The
// preparser does not understand the import keyword (this test is
// run without kAllowHarmonyDynamicImport flag), so this results in
// an "Unexpected token import" error.
RunParserSyncTest(context_data, data, kError);
RunModuleParserSyncTest(context_data, data, kError, NULL, 0, NULL, 0, NULL, 0,
true, true);
static const ParserFlag flags[] = {kAllowHarmonyDynamicImport};
RunParserSyncTest(context_data, data, kSuccess, NULL, 0, flags,
arraysize(flags));
RunModuleParserSyncTest(context_data, data, kSuccess, NULL, 0, flags,
arraysize(flags));
}
TEST(ImportExpressionErrors) {
{
// clang-format off
const char* context_data[][2] = {
{"", ""},
{"var ", ""},
{"let ", ""},
{"new ", ""},
{NULL, NULL}
};
const char* data[] = {
"import(",
"import)",
"import()",
"import('x",
"import('x']",
"import['x')",
"import = x",
"import[",
"import[]",
"import]",
"import[x]",
"import{",
"import{x",
"import{x}",
"import(x, y)",
"import(...y)",
"import(x,)",
"import(,)",
"import(,y)",
"import(;)",
"[import]",
"{import}",
"import+",
"import = 1",
"import.wat",
NULL
};
// clang-format on
RunParserSyncTest(context_data, data, kError);
// We ignore the error messages for the reason explained in the
// ImportExpressionSuccess test.
RunModuleParserSyncTest(context_data, data, kError, NULL, 0, NULL, 0, NULL,
0, true, true);
static const ParserFlag flags[] = {kAllowHarmonyDynamicImport};
RunParserSyncTest(context_data, data, kError, NULL, 0, flags,
arraysize(flags));
// We ignore test error messages because the error message from
// the parser/preparser is different for the same data depending
// on the context. For example, a top level "import{" is parsed
// as an import declaration. The parser parses the import token
// correctly and then shows an "Unexpected end of input" error
// message because of the '{'. The preparser shows an "Unexpected
// token {" because it's not a valid token in a CallExpression.
RunModuleParserSyncTest(context_data, data, kError, NULL, 0, flags,
arraysize(flags), NULL, 0, true, true);
}
{
// clang-format off
const char* context_data[][2] = {
{"var ", ""},
{"let ", ""},
{NULL, NULL}
};
const char* data[] = {
"import('x')",
NULL
};
// clang-format on
RunParserSyncTest(context_data, data, kError);
RunModuleParserSyncTest(context_data, data, kError);
static const ParserFlag flags[] = {kAllowHarmonyDynamicImport};
RunParserSyncTest(context_data, data, kError, NULL, 0, flags,
arraysize(flags));
RunModuleParserSyncTest(context_data, data, kError, NULL, 0, flags,
arraysize(flags));
}
}
TEST(SuperCall) {
const char* context_data[][2] = {{"", ""},