diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 402eb1a7fc..f0017f763a 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -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 native_context, const char* name, Handle 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++) { diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 06cc0cabc3..ea62ccba7a 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -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) \ diff --git a/src/parsing/parser-base.h b/src/parsing/parser-base.h index 18366443b7..e6f93f4a19 100644 --- a/src/parsing/parser-base.h +++ b/src/parsing/parser-base.h @@ -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::ExpressionT ParserBase::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::ExpressionT ParserBase::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::ExpressionT ParserBase::ParseMemberExpression( return result; } +template +typename ParserBase::ExpressionT +ParserBase::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* args = new (zone()) ZoneList(1, zone()); + args->Add(arg, zone()); + return factory()->NewCallRuntime(Runtime::kDynamicImportCall, args, pos); +} + template typename ParserBase::ExpressionT ParserBase::ParseSuperExpression( bool is_new, bool* ok) { diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc index 34a1ef224a..0dd4035349 100644 --- a/src/parsing/parser.cc +++ b/src/parsing/parser.cc @@ -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 diff --git a/src/parsing/preparser.h b/src/parsing/preparser.h index f118139706..2fb603564a 100644 --- a/src/parsing/preparser.h +++ b/src/parsing/preparser.h @@ -759,6 +759,12 @@ class PreParserFactory { return PreParserStatement::Default(); } + PreParserExpression NewCallRuntime(Runtime::FunctionId id, + ZoneList* 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; } diff --git a/src/runtime/runtime-module.cc b/src/runtime/runtime-module.cc index f2a9761203..c73f0ae783 100644 --- a/src/runtime/runtime-module.cc +++ b/src/runtime/runtime-module.cc @@ -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()); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 2923bd8802..aff9779589 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -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) diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index 1a12c5686a..445df738f6 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -1285,6 +1285,7 @@ enum ParserFlag { kAllowHarmonyTrailingCommas, kAllowHarmonyClassFields, kAllowHarmonyObjectRestSpread, + kAllowHarmonyDynamicImport, }; enum ParserSyncTestResult { @@ -1302,6 +1303,7 @@ void SetGlobalFlags(i::EnumSet 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 flags) { @@ -1316,13 +1318,15 @@ void SetParserFlags(i::PreParser* parser, i::EnumSet 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 source, i::EnumSet 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 source, CHECK(false); } // Check that preparser and parser produce the same error. - if (test_preparser) { + if (test_preparser && !ignore_error_msg) { i::Handle 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 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 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 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] = {{"", ""},