ES6: Implement generator method shorthand
authorarv@chromium.org <arv@chromium.org>
Thu, 18 Sep 2014 17:14:13 +0000 (17:14 +0000)
committerarv@chromium.org <arv@chromium.org>
Thu, 18 Sep 2014 17:14:13 +0000 (17:14 +0000)
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
src/preparser.h
src/v8natives.js
test/cctest/test-parsing.cc
test/mjsunit/harmony/object-literals-method.js

index 55bb572..3eb95c6 100644 (file)
@@ -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;
 }
 
 
index 0147caf..f9a3fbe 100644 (file)
@@ -1936,11 +1936,12 @@ template <class Traits>
 typename ParserBase<Traits>::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<Traits>::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<Traits>::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<Traits>::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) {
index 405e7d6..8614dc8 100644 (file)
@@ -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;
 }
 
index aa35009..72f2298 100644 (file)
@@ -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[] = {
index 2af0859..71f44d1 100644 (file)
@@ -5,7 +5,7 @@
 // Flags: --harmony-object-literals --allow-natives-syntax
 
 
-(function TestDescriptor() {
+(function TestBasics() {
   var object = {
     method() {
       return 42;
 })();
 
 
+(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'));
   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());
+})();