From 714f5f401c001b0973f6f3a5f071b9addd01893c Mon Sep 17 00:00:00 2001 From: "arv@chromium.org" Date: Thu, 18 Sep 2014 17:14:13 +0000 Subject: [PATCH] ES6: Implement generator method shorthand https://people.mozilla.org/~jorendorff/es6-draft.html#sec-method-definitions BUG=v8:3516 LOG=Y R=dslomov@chromium.org Review URL: https://codereview.chromium.org/577973002 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24048 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/globals.h | 8 +- src/preparser.h | 20 ++- src/v8natives.js | 7 +- test/cctest/test-parsing.cc | 96 ++++++----- .../mjsunit/harmony/object-literals-method.js | 153 ++++++++++++++++-- 5 files changed, 219 insertions(+), 65 deletions(-) diff --git a/src/globals.h b/src/globals.h index 55bb57289d..3eb95c644c 100644 --- a/src/globals.h +++ b/src/globals.h @@ -761,17 +761,17 @@ enum FunctionKind { kNormalFunction = 0, kArrowFunction = 1, kGeneratorFunction = 2, - kConciseMethod = 4 + kConciseMethod = 4, + kConciseGeneratorMethod = kGeneratorFunction | kConciseMethod }; inline bool IsValidFunctionKind(FunctionKind kind) { - // At the moment these are mutually exclusive but in the future that wont be - // the case since ES6 allows concise generator methods. return kind == FunctionKind::kNormalFunction || kind == FunctionKind::kArrowFunction || kind == FunctionKind::kGeneratorFunction || - kind == FunctionKind::kConciseMethod; + kind == FunctionKind::kConciseMethod || + kind == FunctionKind::kConciseGeneratorMethod; } diff --git a/src/preparser.h b/src/preparser.h index 0147caf5d1..f9a3fbea0b 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -1936,11 +1936,12 @@ template typename ParserBase::ObjectLiteralPropertyT ParserBase< Traits>::ParsePropertyDefinition(ObjectLiteralChecker* checker, bool in_class, bool is_static, bool* ok) { - // TODO(arv): Add support for concise generator methods. ExpressionT value = this->EmptyExpression(); bool is_get = false; bool is_set = false; bool name_is_static = false; + bool is_generator = allow_harmony_object_literals_ && Check(Token::MUL); + Token::Value name_token = peek(); int next_pos = peek_position(); IdentifierT name = @@ -1949,7 +1950,7 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< if (fni_ != NULL) this->PushLiteralName(fni_, name); - if (!in_class && peek() == Token::COLON) { + if (!in_class && !is_generator && peek() == Token::COLON) { // PropertyDefinition : PropertyName ':' AssignmentExpression checker->CheckProperty(name_token, kValueProperty, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); @@ -1957,7 +1958,8 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< value = this->ParseAssignmentExpression( true, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); - } else if (allow_harmony_object_literals_ && peek() == Token::LPAREN) { + } else if (is_generator || + (allow_harmony_object_literals_ && peek() == Token::LPAREN)) { // Concise Method if (is_static && this->IsPrototype(name)) { @@ -1965,14 +1967,22 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< *ok = false; return this->EmptyObjectLiteralProperty(); } + if (is_generator && in_class && !is_static && this->IsConstructor(name)) { + ReportMessageAt(scanner()->location(), "constructor_special_method"); + *ok = false; + return this->EmptyObjectLiteralProperty(); + } checker->CheckProperty(name_token, kValueProperty, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); + FunctionKind kind = is_generator ? FunctionKind::kConciseGeneratorMethod + : FunctionKind::kConciseMethod; + value = this->ParseFunctionLiteral( name, scanner()->location(), false, // reserved words are allowed here - FunctionKind::kConciseMethod, RelocInfo::kNoPosition, - FunctionLiteral::ANONYMOUS_EXPRESSION, FunctionLiteral::NORMAL_ARITY, + kind, RelocInfo::kNoPosition, FunctionLiteral::ANONYMOUS_EXPRESSION, + FunctionLiteral::NORMAL_ARITY, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); } else if (in_class && name_is_static && !is_static) { diff --git a/src/v8natives.js b/src/v8natives.js index 405e7d6790..8614dc8aed 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -1758,11 +1758,10 @@ function FunctionSourceString(func) { ? 'anonymous' : %FunctionGetName(func); - // TODO(arv): Handle concise generator methods. - + var isGenerator = %FunctionIsGenerator(func); var head = %FunctionIsConciseMethod(func) - ? '' - : %FunctionIsGenerator(func) ? 'function* ' : 'function '; + ? (isGenerator ? '*' : '') + : (isGenerator ? 'function* ' : 'function '); return head + name + source; } diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index aa350092a6..72f2298042 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -3411,6 +3411,8 @@ TEST(ErrorsSuper) { TEST(NoErrorsMethodDefinition) { const char* context_data[][2] = {{"({", "});"}, {"'use strict'; ({", "});"}, + {"({*", "});"}, + {"'use strict'; ({*", "});"}, {NULL, NULL}}; const char* object_literal_body_data[] = { @@ -3431,6 +3433,8 @@ TEST(NoErrorsMethodDefinition) { TEST(MethodDefinitionNames) { const char* context_data[][2] = {{"({", "(x, y) {}});"}, {"'use strict'; ({", "(x, y) {}});"}, + {"({*", "(x, y) {}});"}, + {"'use strict'; ({*", "(x, y) {}});"}, {NULL, NULL}}; const char* name_data[] = { @@ -3505,6 +3509,8 @@ TEST(MethodDefinitionNames) { TEST(MethodDefinitionStrictFormalParamereters) { const char* context_data[][2] = {{"({method(", "){}});"}, {"'use strict'; ({method(", "){}});"}, + {"({*method(", "){}});"}, + {"'use strict'; ({*method(", "){}});"}, {NULL, NULL}}; const char* params_data[] = { @@ -3540,6 +3546,18 @@ TEST(MethodDefinitionDuplicateProperty) { "x() {}, 'x'() {}", "0() {}, '0'() {}", "1.0() {}, 1: 1", + + "x: 1, *x() {}", + "*x() {}, x: 1", + "*x() {}, get x() {}", + "*x() {}, set x(_) {}", + "*x() {}, *x() {}", + "*x() {}, y() {}, *x() {}", + "*x() {}, *\"x\"() {}", + "*x() {}, *'x'() {}", + "*0() {}, *'0'() {}", + "*1.0() {}, 1: 1", + NULL }; @@ -3549,7 +3567,7 @@ TEST(MethodDefinitionDuplicateProperty) { } -TEST(NoErrorsClassExpression) { +TEST(ClassExpressionNoErrors) { const char* context_data[][2] = {{"(", ");"}, {"var C = ", ";"}, {"bar, ", ";"}, @@ -3573,7 +3591,7 @@ TEST(NoErrorsClassExpression) { } -TEST(NoErrorsClassDeclaration) { +TEST(ClassDeclarationNoErrors) { const char* context_data[][2] = {{"", ""}, {"{", "}"}, {"if (true) {", "}"}, @@ -3592,7 +3610,7 @@ TEST(NoErrorsClassDeclaration) { } -TEST(NoErrorsClassBody) { +TEST(ClassBodyNoErrors) { // Tests that parser and preparser accept valid class syntax. const char* context_data[][2] = {{"(class {", "});"}, {"(class extends Base {", "});"}, @@ -3604,12 +3622,16 @@ TEST(NoErrorsClassBody) { ";;", "m() {}", "m() {};", - ";m() {}", + "; m() {}", "m() {}; n(x) {}", "get x() {}", "set x(v) {}", "get() {}", "set() {}", + "*g() {}", + "*g() {};", + "; *g() {}", + "*g() {}; *h(x) {}", "static() {}", "static m() {}", "static get x() {}", @@ -3619,6 +3641,10 @@ TEST(NoErrorsClassBody) { "static static() {}", "static get static() {}", "static set static(v) {}", + "*static() {}", + "*get() {}", + "*set() {}", + "static *g() {}", NULL}; static const ParserFlag always_flags[] = { @@ -3630,39 +3656,23 @@ TEST(NoErrorsClassBody) { } -TEST(MethodDefinitionstrictFormalParamereters) { - const char* context_data[][2] = {{"({method(", "){}});"}, - {NULL, NULL}}; - - const char* params_data[] = { - "x, x", - "x, y, x", - "eval", - "arguments", - "var", - "const", - NULL - }; - - static const ParserFlag always_flags[] = {kAllowHarmonyObjectLiterals}; - RunParserSyncTest(context_data, params_data, kError, NULL, 0, - always_flags, arraysize(always_flags)); -} - - -TEST(NoErrorsClassPropertyName) { +TEST(ClassPropertyNameNoErrors) { const char* context_data[][2] = {{"(class {", "() {}});"}, {"(class { get ", "() {}});"}, {"(class { set ", "(v) {}});"}, {"(class { static ", "() {}});"}, {"(class { static get ", "() {}});"}, {"(class { static set ", "(v) {}});"}, + {"(class { *", "() {}});"}, + {"(class { static *", "() {}});"}, {"class C {", "() {}}"}, {"class C { get ", "() {}}"}, {"class C { set ", "(v) {}}"}, {"class C { static ", "() {}}"}, {"class C { static get ", "() {}}"}, {"class C { static set ", "(v) {}}"}, + {"class C { *", "() {}}"}, + {"class C { static *", "() {}}"}, {NULL, NULL}}; const char* name_data[] = { "42", @@ -3704,7 +3714,7 @@ TEST(NoErrorsClassPropertyName) { } -TEST(ErrorsClassExpression) { +TEST(ClassExpressionErrors) { const char* context_data[][2] = {{"(", ");"}, {"var C = ", ";"}, {"bar, ", ";"}, @@ -3735,7 +3745,7 @@ TEST(ErrorsClassExpression) { } -TEST(ErrorsClassDeclaration) { +TEST(ClassDeclarationErrors) { const char* context_data[][2] = {{"", ""}, {"{", "}"}, {"if (true) {", "}"}, @@ -3750,11 +3760,17 @@ TEST(ErrorsClassDeclaration) { "class name { m; n }", "class name { m: 1 }", "class name { m(); n() }", - "class name { get m }", - "class name { get m() }", - "class name { set m() {) }", // missing required param + "class name { get x }", + "class name { get x() }", + "class name { set x() {) }", // missing required param "class {}", // Name is required for declaration "class extends base {}", + "class name { *", + "class name { * }", + "class name { *; }", + "class name { *get x() {} }", + "class name { *set x(_) {} }", + "class name { *static m() {} }", NULL}; static const ParserFlag always_flags[] = { @@ -3766,7 +3782,7 @@ TEST(ErrorsClassDeclaration) { } -TEST(ErrorsClassName) { +TEST(ClassNameErrors) { const char* context_data[][2] = {{"class ", "{}"}, {"(class ", "{});"}, {"'use strict'; class ", "{}"}, @@ -3796,7 +3812,7 @@ TEST(ErrorsClassName) { } -TEST(ErrorsClassGetterParamName) { +TEST(ClassGetterParamNameErrors) { const char* context_data[][2] = { {"class C { get name(", ") {} }"}, {"(class { get name(", ") {} });"}, @@ -3829,7 +3845,7 @@ TEST(ErrorsClassGetterParamName) { } -TEST(ErrorsClassStaticPrototype) { +TEST(ClassStaticPrototypeErrors) { const char* context_data[][2] = {{"class C {", "}"}, {"(class {", "});"}, {NULL, NULL}}; @@ -3838,6 +3854,7 @@ TEST(ErrorsClassStaticPrototype) { "static prototype() {}", "static get prototype() {}", "static set prototype(_) {}", + "static *prototype() {}", NULL}; static const ParserFlag always_flags[] = { @@ -3849,7 +3866,7 @@ TEST(ErrorsClassStaticPrototype) { } -TEST(ErrorsClassSpecialConstructor) { +TEST(ClassSpecialConstructorErrors) { const char* context_data[][2] = {{"class C {", "}"}, {"(class {", "});"}, {NULL, NULL}}; @@ -3857,6 +3874,7 @@ TEST(ErrorsClassSpecialConstructor) { const char* class_body_data[] = { "get constructor() {}", "get constructor(_) {}", + "*constructor() {}", NULL}; static const ParserFlag always_flags[] = { @@ -3868,7 +3886,7 @@ TEST(ErrorsClassSpecialConstructor) { } -TEST(NoErrorsClassConstructor) { +TEST(ClassConstructorNoErrors) { const char* context_data[][2] = {{"class C {", "}"}, {"(class {", "});"}, {NULL, NULL}}; @@ -3878,6 +3896,7 @@ TEST(NoErrorsClassConstructor) { "static constructor() {}", "static get constructor() {}", "static set constructor(_) {}", + "static *constructor() {}", NULL}; static const ParserFlag always_flags[] = { @@ -3889,7 +3908,7 @@ TEST(NoErrorsClassConstructor) { } -TEST(ErrorsClassMultipleConstructor) { +TEST(ClassMultipleConstructorErrors) { // We currently do not allow any duplicate properties in class bodies. This // test ensures that when we change that we still throw on duplicate // constructors. @@ -3912,7 +3931,7 @@ TEST(ErrorsClassMultipleConstructor) { // TODO(arv): We should allow duplicate property names. // https://code.google.com/p/v8/issues/detail?id=3570 -DISABLED_TEST(NoErrorsClassMultiplePropertyNames) { +DISABLED_TEST(ClassMultiplePropertyNamesNoErrors) { const char* context_data[][2] = {{"class C {", "}"}, {"(class {", "});"}, {NULL, NULL}}; @@ -3932,7 +3951,7 @@ DISABLED_TEST(NoErrorsClassMultiplePropertyNames) { } -TEST(ErrorsClassesAreStrict) { +TEST(ClassesAreStrictErrors) { const char* context_data[][2] = {{"", ""}, {"(", ");"}, {NULL, NULL}}; @@ -3940,6 +3959,7 @@ TEST(ErrorsClassesAreStrict) { const char* class_body_data[] = { "class C { method() { with ({}) {} } }", "class C extends function() { with ({}) {} } {}", + "class C { *method() { with ({}) {} } }", NULL}; static const ParserFlag always_flags[] = { diff --git a/test/mjsunit/harmony/object-literals-method.js b/test/mjsunit/harmony/object-literals-method.js index 2af0859424..71f44d10bc 100644 --- a/test/mjsunit/harmony/object-literals-method.js +++ b/test/mjsunit/harmony/object-literals-method.js @@ -5,7 +5,7 @@ // Flags: --harmony-object-literals --allow-natives-syntax -(function TestDescriptor() { +(function TestBasics() { var object = { method() { return 42; @@ -15,6 +15,16 @@ })(); +(function TestThis() { + var object = { + method() { + assertEquals(object, this); + } + }; + object.method(); +})(); + + (function TestDescriptor() { var object = { method() { @@ -34,9 +44,7 @@ (function TestProto() { var object = { - method() { - return 42; - } + method() {} }; assertEquals(Function.prototype, Object.getPrototypeOf(object.method)); @@ -45,9 +53,7 @@ (function TestNotConstructable() { var object = { - method() { - return 42; - } + method() {} }; assertThrows(function() { @@ -58,9 +64,7 @@ (function TestFunctionName() { var object = { - method() { - return 42; - }, + method() {}, 1() {}, 2.0() {} }; @@ -68,7 +72,6 @@ assertEquals('method', f.name); var g = object[1]; assertEquals('1', g.name); - var h = object[2]; assertEquals('2', h.name); })(); @@ -90,9 +93,7 @@ (function TestNoPrototype() { var object = { - method() { - return 42; - } + method() {} }; var f = object.method; assertFalse(f.hasOwnProperty('prototype')); @@ -121,3 +122,127 @@ assertEquals(42, object.method()); assertFalse(object.method.hasOwnProperty('prototype')); })(); + + +/////////////////////////////////////////////////////////////////////////////// + + +var GeneratorFunction = function*() {}.__proto__.constructor; + + +function assertIteratorResult(value, done, result) { + assertEquals({value: value, done: done}, result); +} + + +(function TestGeneratorBasics() { + var object = { + *method() { + yield 1; + } + }; + var g = object.method(); + assertIteratorResult(1, false, g.next()); + assertIteratorResult(undefined, true, g.next()); +})(); + + +(function TestGeneratorThis() { + var object = { + *method() { + yield this; + } + }; + var g = object.method(); + assertIteratorResult(object, false, g.next()); + assertIteratorResult(undefined, true, g.next()); +})(); + + +(function TestGeneratorSymbolIterator() { + var object = { + *method() {} + }; + var g = object.method(); + assertEquals(g, g[Symbol.iterator]()); +})(); + + +(function TestGeneratorDescriptor() { + var object = { + *method() { + yield 1; + } + }; + + var desc = Object.getOwnPropertyDescriptor(object, 'method'); + assertTrue(desc.enumerable); + assertTrue(desc.configurable); + assertTrue(desc.writable); + assertEquals('function', typeof desc.value); + + var g = desc.value(); + assertIteratorResult(1, false, g.next()); + assertIteratorResult(undefined, true, g.next()); +})(); + + +(function TestGeneratorProto() { + var object = { + *method() {} + }; + + assertEquals(GeneratorFunction.prototype, + Object.getPrototypeOf(object.method)); +})(); + + +(function TestGeneratorConstructable() { + var object = { + *method() { + yield 1; + } + }; + + var g = new object.method(); + assertIteratorResult(1, false, g.next()); + assertIteratorResult(undefined, true, g.next()); +})(); + + +(function TestGeneratorName() { + var object = { + *method() {}, + *1() {}, + *2.0() {} + }; + var f = object.method; + assertEquals('method', f.name); + var g = object[1]; + assertEquals('1', g.name); + var h = object[2]; + assertEquals('2', h.name); +})(); + + +(function TestGeneratorNoBinding() { + var method = 'local'; + var calls = 0; + var object = { + *method() { + calls++; + assertEquals('local', method); + } + }; + var g = object.method(); + assertIteratorResult(undefined, true, g.next()); + assertEquals(1, calls); +})(); + + +(function TestGeneratorToString() { + var object = { + *method() { yield 1; } + }; + assertEquals('*method() { yield 1; }', object.method.toString()); +})();