This uses a runtime function to set up the the constructor and its
authorarv@chromium.org <arv@chromium.org>
Tue, 7 Oct 2014 16:24:59 +0000 (16:24 +0000)
committerarv@chromium.org <arv@chromium.org>
Tue, 7 Oct 2014 16:24:59 +0000 (16:24 +0000)
prototype.

This does not add the methods/accessors to the prototype or the
constructor.

BUG=v8:3330
LOG=Y
R=dslomov@chromium.org

Review URL: https://codereview.chromium.org/631433002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24442 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/compiler/ast-graph-builder.cc
src/compiler/pipeline.cc
src/full-codegen.cc
src/messages.js
src/parser.cc
src/parser.h
src/preparser.h
src/runtime/runtime-classes.cc
src/runtime/runtime.h
test/mjsunit/harmony/classes.js [new file with mode: 0644]

index fa98779..fa3e88b 100644 (file)
@@ -813,7 +813,6 @@ void AstGraphBuilder::VisitFunctionLiteral(FunctionLiteral* expr) {
 
 
 void AstGraphBuilder::VisitClassLiteral(ClassLiteral* expr) {
-  // TODO(arv): Implement.
   UNREACHABLE();
 }
 
index ca58e17..469a7ed 100644 (file)
@@ -145,7 +145,7 @@ class AstGraphBuilderWithPositions : public AstGraphBuilder {
   }
 
 #define DEF_VISIT(type)                                               \
-  virtual void Visit##type(type* node) OVERRIDE {                  \
+  virtual void Visit##type(type* node) OVERRIDE {                     \
     SourcePositionTable::Scope pos(source_positions_,                 \
                                    SourcePosition(node->position())); \
     AstGraphBuilder::Visit##type(node);                               \
@@ -173,7 +173,7 @@ Handle<Code> Pipeline::GenerateCode() {
       info()->function()->dont_optimize_reason() == kForOfStatement ||
       // TODO(turbofan): Make super work and remove this bailout.
       info()->function()->dont_optimize_reason() == kSuperReference ||
-      // TODO(turbofan): Make classliterals work and remove this bailout.
+      // TODO(turbofan): Make class literals work and remove this bailout.
       info()->function()->dont_optimize_reason() == kClassLiteral ||
       // TODO(turbofan): Make OSR work and remove this bailout.
       info()->is_osr()) {
index 35d51d9..8177a35 100644 (file)
@@ -1542,12 +1542,30 @@ void FullCodeGenerator::VisitFunctionLiteral(FunctionLiteral* expr) {
 
 
 void FullCodeGenerator::VisitClassLiteral(ClassLiteral* expr) {
-  // TODO(arv): Implement
   Comment cmnt(masm_, "[ ClassLiteral");
+
+  if (expr->raw_name() != NULL) {
+    __ Push(expr->name());
+  } else {
+    __ Push(isolate()->factory()->undefined_value());
+  }
+
   if (expr->extends() != NULL) {
-    VisitForEffect(expr->extends());
+    VisitForStackValue(expr->extends());
+  } else {
+    __ Push(isolate()->factory()->the_hole_value());
   }
-  context()->Plug(isolate()->factory()->undefined_value());
+
+  if (expr->constructor() != NULL) {
+    VisitForStackValue(expr->constructor());
+  } else {
+    __ Push(isolate()->factory()->undefined_value());
+  }
+
+  // TODO(arv): Process methods
+
+  __ CallRuntime(Runtime::kDefineClass, 3);
+  context()->Plug(result_register());
 }
 
 
index 4a71a61..2dddffa 100644 (file)
@@ -174,7 +174,9 @@ var kMessages = {
   invalid_module_path:           ["Module does not export '", "%0", "', or export is not itself a module"],
   module_type_error:             ["Module '", "%0", "' used improperly"],
   module_export_undefined:       ["Export '", "%0", "' is not defined in module"],
-  unexpected_super:              ["'super' keyword unexpected here"]
+  unexpected_super:              ["'super' keyword unexpected here"],
+  extends_value_not_a_function:  ["Class extends value ", "%0", " is not a function or null"],
+  prototype_parent_not_an_object: ["Class extends value does not have valid prototype property ", "%0"]
 };
 
 
index 2b91443..04efd1e 100644 (file)
@@ -646,7 +646,7 @@ Expression* ParserTraits::SuperReference(
       pos);
 }
 
-Expression* ParserTraits::ClassLiteral(
+Expression* ParserTraits::ClassExpression(
     const AstRawString* name, Expression* extends, Expression* constructor,
     ZoneList<ObjectLiteral::Property*>* properties, int pos,
     AstNodeFactory<AstConstructionVisitor>* factory) {
@@ -1956,21 +1956,18 @@ Statement* Parser::ParseClassDeclaration(ZoneList<const AstRawString*>* names,
   Expression* value = ParseClassLiteral(name, scanner()->location(),
                                         is_strict_reserved, pos, CHECK_OK);
 
-  Block* block = factory()->NewBlock(NULL, 1, true, pos);
-  VariableMode mode = LET;
-  VariableProxy* proxy = NewUnresolved(name, mode, Interface::NewValue());
+  VariableProxy* proxy = NewUnresolved(name, LET, Interface::NewValue());
   Declaration* declaration =
-      factory()->NewVariableDeclaration(proxy, mode, scope_, pos);
+      factory()->NewVariableDeclaration(proxy, LET, scope_, pos);
   Declare(declaration, true, CHECK_OK);
+  proxy->var()->set_initializer_position(pos);
 
   Token::Value init_op = Token::INIT_LET;
   Assignment* assignment = factory()->NewAssignment(init_op, proxy, value, pos);
-  block->AddStatement(
-      factory()->NewExpressionStatement(assignment, RelocInfo::kNoPosition),
-      zone());
-
+  Statement* assignment_statement =
+      factory()->NewExpressionStatement(assignment, RelocInfo::kNoPosition);
   if (names) names->Add(name, zone());
-  return block;
+  return assignment_statement;
 }
 
 
index 300cc03..e461a99 100644 (file)
@@ -417,6 +417,15 @@ class ParserTraits {
     return string->AsArrayIndex(index);
   }
 
+  bool IsConstructorProperty(ObjectLiteral::Property* property) {
+    return property->key()->raw_value()->EqualsString(
+        ast_value_factory()->constructor_string());
+  }
+
+  static Expression* GetPropertyValue(ObjectLiteral::Property* property) {
+    return property->value();
+  }
+
   // Functions for encapsulating the differences between parsing and preparsing;
   // operations interleaved with the recursive descent.
   static void PushLiteralName(FuncNameInferrer* fni, const AstRawString* id) {
@@ -546,11 +555,11 @@ class ParserTraits {
   Expression* SuperReference(Scope* scope,
                              AstNodeFactory<AstConstructionVisitor>* factory,
                              int pos = RelocInfo::kNoPosition);
-  Expression* ClassLiteral(const AstRawString* name, Expression* extends,
-                           Expression* constructor,
-                           ZoneList<ObjectLiteral::Property*>* properties,
-                           int pos,
-                           AstNodeFactory<AstConstructionVisitor>* factory);
+  Expression* ClassExpression(const AstRawString* name, Expression* extends,
+                              Expression* constructor,
+                              ZoneList<ObjectLiteral::Property*>* properties,
+                              int pos,
+                              AstNodeFactory<AstConstructionVisitor>* factory);
 
   Literal* ExpressionFromLiteral(
       Token::Value token, int pos, Scanner* scanner,
index 5c2a622..1def94f 100644 (file)
@@ -150,6 +150,13 @@ class ParserBase : public Traits {
           scope_(scope) {
       *scope_stack_ = scope_;
     }
+    BlockState(typename Traits::Type::Scope** scope_stack,
+               typename Traits::Type::Scope** scope)
+        : scope_stack_(scope_stack),
+          outer_scope_(*scope_stack),
+          scope_(*scope) {
+      *scope_stack_ = scope_;
+    }
     ~BlockState() { *scope_stack_ = outer_scope_; }
 
    private:
@@ -1195,6 +1202,13 @@ class PreParserTraits {
     return false;
   }
 
+  bool IsConstructorProperty(PreParserExpression property) { return false; }
+
+  static PreParserExpression GetPropertyValue(PreParserExpression property) {
+    UNREACHABLE();
+    return PreParserExpression::Default();
+  }
+
   // Functions for encapsulating the differences between parsing and preparsing;
   // operations interleaved with the recursive descent.
   static void PushLiteralName(FuncNameInferrer* fni, PreParserIdentifier id) {
@@ -1320,12 +1334,12 @@ class PreParserTraits {
     return PreParserExpression::Super();
   }
 
-  static PreParserExpression ClassLiteral(PreParserIdentifier name,
-                                          PreParserExpression extends,
-                                          PreParserExpression constructor,
-                                          PreParserExpressionList properties,
-                                          int position,
-                                          PreParserFactory* factory) {
+  static PreParserExpression ClassExpression(PreParserIdentifier name,
+                                             PreParserExpression extends,
+                                             PreParserExpression constructor,
+                                             PreParserExpressionList properties,
+                                             int position,
+                                             PreParserFactory* factory) {
     return PreParserExpression::Default();
   }
 
@@ -1978,16 +1992,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();
+
+    FunctionKind kind = is_generator ? FunctionKind::kConciseGeneratorMethod
+                                     : FunctionKind::kConciseMethod;
+
+    if (in_class && !is_static && this->IsConstructor(name)) {
+      if (is_generator) {
+        ReportMessageAt(scanner()->location(), "constructor_special_method");
+        *ok = false;
+        return this->EmptyObjectLiteralProperty();
+      }
+
+      kind = FunctionKind::kNormalFunction;
     }
 
     checker->CheckProperty(name_token, kValueProperty,
                            CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
-    FunctionKind kind = is_generator ? FunctionKind::kConciseGeneratorMethod
-                                     : FunctionKind::kConciseMethod;
 
     value = this->ParseFunctionLiteral(
         name, scanner()->location(),
@@ -2745,25 +2765,24 @@ typename ParserBase<Traits>::ExpressionT ParserBase<Traits>::ParseClassLiteral(
     return this->EmptyExpression();
   }
 
-  // TODO(arv): Implement scopes and name binding in class body only.
-  // TODO(arv): Maybe add CLASS_SCOPE?
-  typename Traits::Type::ScopePtr extends_scope =
-      this->NewScope(scope_, BLOCK_SCOPE);
-  FunctionState extends_function_state(
-      &function_state_, &scope_, &extends_scope, zone(),
-      this->ast_value_factory(), ast_node_id_gen_);
-  scope_->SetStrictMode(STRICT);
-  scope_->SetScopeName(name);
-
   ExpressionT extends = this->EmptyExpression();
   if (Check(Token::EXTENDS)) {
+    typename Traits::Type::ScopePtr scope = this->NewScope(scope_, BLOCK_SCOPE);
+    BlockState block_state(&scope_, &scope);
+    scope_->SetStrictMode(STRICT);
     extends = this->ParseLeftHandSideExpression(CHECK_OK);
   }
 
+  // TODO(arv): Implement scopes and name binding in class body only.
+  typename Traits::Type::ScopePtr scope = this->NewScope(scope_, BLOCK_SCOPE);
+  BlockState block_state(&scope_, &scope);
+  scope_->SetStrictMode(STRICT);
+  scope_->SetScopeName(name);
+
   ObjectLiteralChecker checker(this, STRICT);
   typename Traits::Type::PropertyList properties =
       this->NewPropertyList(4, zone_);
-  FunctionLiteralT constructor = this->EmptyFunctionLiteral();
+  ExpressionT constructor = this->EmptyExpression();
 
   Expect(Token::LBRACE, CHECK_OK);
   while (peek() != Token::RBRACE) {
@@ -2775,7 +2794,11 @@ typename ParserBase<Traits>::ExpressionT ParserBase<Traits>::ParseClassLiteral(
     ObjectLiteralPropertyT property =
         this->ParsePropertyDefinition(&checker, in_class, is_static, CHECK_OK);
 
-    properties->Add(property, zone());
+    if (this->IsConstructorProperty(property)) {
+      constructor = this->GetPropertyValue(property);
+    } else {
+      properties->Add(property, zone());
+    }
 
     if (fni_ != NULL) {
       fni_->Infer();
@@ -2784,8 +2807,8 @@ typename ParserBase<Traits>::ExpressionT ParserBase<Traits>::ParseClassLiteral(
   }
   Expect(Token::RBRACE, CHECK_OK);
 
-  return this->ClassLiteral(name, extends, constructor, properties, pos,
-                            factory());
+  return this->ClassExpression(name, extends, constructor, properties, pos,
+                               factory());
 }
 
 
index 20e98ef..b8cfa9b 100644 (file)
@@ -151,5 +151,84 @@ RUNTIME_FUNCTION(Runtime_StoreToSuper_Sloppy) {
 
   return StoreToSuper(isolate, home_object, receiver, name, value, SLOPPY);
 }
+
+
+RUNTIME_FUNCTION(Runtime_DefineClass) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 3);
+  CONVERT_ARG_HANDLE_CHECKED(Object, name, 0);
+  CONVERT_ARG_HANDLE_CHECKED(Object, super_class, 1);
+  CONVERT_ARG_HANDLE_CHECKED(Object, constructor, 2);
+
+  Handle<Object> prototype_parent;
+  Handle<Object> constructor_parent;
+
+  if (super_class->IsTheHole()) {
+    prototype_parent = isolate->initial_object_prototype();
+  } else {
+    if (super_class->IsNull()) {
+      prototype_parent = isolate->factory()->null_value();
+    } else if (super_class->IsSpecFunction()) {
+      ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+          isolate, prototype_parent,
+          Runtime::GetObjectProperty(isolate, super_class,
+                                     isolate->factory()->prototype_string()));
+      if (!prototype_parent->IsNull() && !prototype_parent->IsSpecObject()) {
+        Handle<Object> args[1] = {prototype_parent};
+        THROW_NEW_ERROR_RETURN_FAILURE(
+            isolate, NewTypeError("prototype_parent_not_an_object",
+                                  HandleVector(args, 1)));
+      }
+      constructor_parent = super_class;
+    } else {
+      // TODO(arv): Should be IsConstructor.
+      Handle<Object> args[1] = {super_class};
+      THROW_NEW_ERROR_RETURN_FAILURE(
+          isolate,
+          NewTypeError("extends_value_not_a_function", HandleVector(args, 1)));
+    }
+  }
+
+  Handle<Map> map =
+      isolate->factory()->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
+  map->set_prototype(*prototype_parent);
+  Handle<JSObject> prototype = isolate->factory()->NewJSObjectFromMap(map);
+
+  Handle<String> name_string = name->IsString()
+                                   ? Handle<String>::cast(name)
+                                   : isolate->factory()->empty_string();
+
+  Handle<JSFunction> ctor;
+  if (constructor->IsSpecFunction()) {
+    ctor = Handle<JSFunction>::cast(constructor);
+    JSFunction::SetPrototype(ctor, prototype);
+    PropertyAttributes attribs =
+        static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE | READ_ONLY);
+    RETURN_FAILURE_ON_EXCEPTION(
+        isolate,
+        JSObject::SetOwnPropertyIgnoreAttributes(
+            ctor, isolate->factory()->prototype_string(), prototype, attribs));
+  } else {
+    // TODO(arv): This should not use an empty function but a function that
+    // calls super.
+    Handle<Code> code(isolate->builtins()->builtin(Builtins::kEmptyFunction));
+    ctor = isolate->factory()->NewFunction(name_string, code, prototype, true);
+  }
+
+  Handle<Symbol> home_object_symbol(isolate->heap()->home_object_symbol());
+  RETURN_FAILURE_ON_EXCEPTION(
+      isolate, JSObject::SetOwnPropertyIgnoreAttributes(
+                   ctor, home_object_symbol, prototype, DONT_ENUM));
+
+  if (!constructor_parent.is_null()) {
+    RETURN_FAILURE_ON_EXCEPTION(
+        isolate, JSObject::SetPrototype(ctor, constructor_parent, false));
+  }
+
+  JSObject::AddProperty(prototype, isolate->factory()->constructor_string(),
+                        ctor, DONT_ENUM);
+
+  return *ctor;
+}
 }
 }  // namespace v8::internal
index 7997415..b42063a 100644 (file)
@@ -192,7 +192,8 @@ namespace internal {
   F(LoadFromSuper, 3, 1)                                   \
   F(LoadKeyedFromSuper, 3, 1)                              \
   F(StoreToSuper_Strict, 4, 1)                             \
-  F(StoreToSuper_Sloppy, 4, 1)
+  F(StoreToSuper_Sloppy, 4, 1)                             \
+  F(DefineClass, 3, 1)
 
 
 #define RUNTIME_FUNCTION_LIST_ALWAYS_2(F)              \
diff --git a/test/mjsunit/harmony/classes.js b/test/mjsunit/harmony/classes.js
new file mode 100644 (file)
index 0000000..7156dbd
--- /dev/null
@@ -0,0 +1,167 @@
+// Copyright 2014 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: --harmony
+
+(function TestBasics() {
+  var C = class C {}
+  assertEquals(typeof C, 'function');
+  assertEquals(C.__proto__, Function.prototype);
+  assertEquals(Object.prototype, Object.getPrototypeOf(C.prototype));
+  assertEquals(Function.prototype, Object.getPrototypeOf(C));
+  assertEquals('C', C.name);
+
+  class D {}
+  assertEquals(typeof D, 'function');
+  assertEquals(D.__proto__, Function.prototype);
+  assertEquals(Object.prototype, Object.getPrototypeOf(D.prototype));
+  assertEquals(Function.prototype, Object.getPrototypeOf(D));
+  assertEquals('D', D.name);
+
+  var E = class {}
+  assertEquals('', E.name);
+})();
+
+
+(function TestBasicsExtends() {
+  class C extends null {}
+  assertEquals(typeof C, 'function');
+  assertEquals(C.__proto__, Function.prototype);
+  assertEquals(null, Object.getPrototypeOf(C.prototype));
+
+  class D extends C {}
+  assertEquals(typeof D, 'function');
+  assertEquals(D.__proto__, C);
+  assertEquals(C.prototype, Object.getPrototypeOf(D.prototype));
+})();
+
+
+(function TestSideEffectInExtends() {
+  var calls = 0;
+  class C {}
+  class D extends (calls++, C) {}
+  assertEquals(1, calls);
+  assertEquals(typeof D, 'function');
+  assertEquals(D.__proto__, C);
+  assertEquals(C.prototype, Object.getPrototypeOf(D.prototype));
+})();
+
+
+(function TestInvalidExtends() {
+  assertThrows(function() {
+    class C extends 42 {}
+  }, TypeError);
+
+  assertThrows(function() {
+    // Function but its .prototype is not null or a function.
+    class C extends Math.abs {}
+  }, TypeError);
+
+  assertThrows(function() {
+    Math.abs.prototype = 42;
+    class C extends Math.abs {}
+  }, TypeError);
+  delete Math.abs.prototype;
+})();
+
+
+(function TestConstructorProperty() {
+  class C {}
+  assertEquals(C, C.prototype.constructor);
+  var descr = Object.getOwnPropertyDescriptor(C.prototype, 'constructor');
+  assertTrue(descr.configurable);
+  assertFalse(descr.enumerable);
+  assertTrue(descr.writable);
+})();
+
+
+(function TestPrototypeProperty() {
+  class C {}
+  var descr = Object.getOwnPropertyDescriptor(C, 'prototype');
+  assertFalse(descr.configurable);
+  assertFalse(descr.enumerable);
+  assertFalse(descr.writable);
+})();
+
+
+(function TestConstructor() {
+  var count = 0;
+  class C {
+    constructor() {
+      assertEquals(Object.getPrototypeOf(this), C.prototype);
+      count++;
+    }
+  }
+  assertEquals(C, C.prototype.constructor);
+  var descr = Object.getOwnPropertyDescriptor(C.prototype, 'constructor');
+  assertTrue(descr.configurable);
+  assertFalse(descr.enumerable);
+  assertTrue(descr.writable);
+
+  var c = new C();
+  assertEquals(1, count);
+  assertEquals(Object.getPrototypeOf(c), C.prototype);
+})();
+
+
+(function TestImplicitConstructor() {
+  class C {}
+  var c = new C();
+  assertEquals(Object.getPrototypeOf(c), C.prototype);
+})();
+
+
+(function TestConstructorStrict() {
+  class C {
+    constructor() {
+      assertThrows(function() {
+        nonExistingBinding = 42;
+      }, ReferenceError);
+    }
+  }
+  new C();
+})();
+
+
+(function TestSuperInConstructor() {
+  var calls = 0;
+  class B {}
+  B.prototype.x = 42;
+
+  class C extends B {
+    constructor() {
+      calls++;
+      assertEquals(42, super.x);
+    }
+  }
+
+  new C;
+  assertEquals(1, calls);
+})();
+
+
+(function TestStrictMode() {
+  class C {}
+
+  with ({a: 1}) {
+    assertEquals(1, a);
+  }
+
+  assertThrows('class C extends function B() { with ({}); return B; }() {}',
+               SyntaxError);
+
+})();
+
+/* TODO(arv): Implement
+(function TestNameBindingInConstructor() {
+  class C {
+    constructor() {
+      assertThrows(function() {
+        C = 42;
+      }, ReferenceError);
+    }
+  }
+  new C();
+})();
+*/