Add support for the new.target syntax.
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Jul 2015 18:18:42 +0000 (18:18 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Jul 2015 18:18:42 +0000 (18:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=147051

Patch by Keith Miller <keith_miller@apple.com> on 2015-07-21
Reviewed by Yusuke Suzuki.

Add support for new.target. Essentially the implementation is, before constructor calls,
the target of a "new" is placed where "this" noramlly goes in the calling convention.
Then in the constructor before object is initialized we move the target of the "new"
into a local variable.

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator):
* bytecompiler/NodesCodegen.cpp:
(JSC::NewTargetNode::emitBytecode):
* parser/ASTBuilder.h:
(JSC::ASTBuilder::newTargetExpr):
* parser/NodeConstructors.h:
(JSC::NewTargetNode::NewTargetNode):
* parser/Nodes.h:
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseMemberExpression):
* parser/SyntaxChecker.h:
(JSC::SyntaxChecker::newTargetExpr):
* runtime/CommonIdentifiers.h:
* tests/stress/new-target.js: Added.
(test):
(call):
(Constructor.subCall):
(Constructor.SubConstructor):
(Constructor):
(noAssign):
(doWeirdThings):
(SuperClass):
(SubClass):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@187108 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/parser/ASTBuilder.h
Source/JavaScriptCore/parser/NodeConstructors.h
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/parser/Parser.cpp
Source/JavaScriptCore/parser/SyntaxChecker.h
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/JavaScriptCore/tests/stress/new-target.js [new file with mode: 0644]

index 081bdc2..e99dd04 100644 (file)
@@ -1,3 +1,40 @@
+2015-07-21  Keith Miller  <keith_miller@apple.com>
+
+        Add support for the new.target syntax.
+        https://bugs.webkit.org/show_bug.cgi?id=147051
+
+        Reviewed by Yusuke Suzuki.
+
+        Add support for new.target. Essentially the implementation is, before constructor calls,
+        the target of a "new" is placed where "this" noramlly goes in the calling convention.
+        Then in the constructor before object is initialized we move the target of the "new"
+        into a local variable.
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::BytecodeGenerator):
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::NewTargetNode::emitBytecode):
+        * parser/ASTBuilder.h:
+        (JSC::ASTBuilder::newTargetExpr):
+        * parser/NodeConstructors.h:
+        (JSC::NewTargetNode::NewTargetNode):
+        * parser/Nodes.h:
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseMemberExpression):
+        * parser/SyntaxChecker.h:
+        (JSC::SyntaxChecker::newTargetExpr):
+        * runtime/CommonIdentifiers.h:
+        * tests/stress/new-target.js: Added.
+        (test):
+        (call):
+        (Constructor.subCall):
+        (Constructor.SubConstructor):
+        (Constructor):
+        (noAssign):
+        (doWeirdThings):
+        (SuperClass):
+        (SubClass):
+
 2015-07-20  Saam barati  <saambarati1@gmail.com>
 
         "let" scoping introduced incoherent story about symbol table cloning
index 9b2c2cd..0f90d63 100644 (file)
@@ -500,10 +500,10 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
         }
     }
     
+    m_newTargetRegister = addVar();
     if (isConstructor()) {
+        emitMove(m_newTargetRegister, &m_thisRegister);
         if (constructorKind() == ConstructorKind::Derived) {
-            m_newTargetRegister = addVar();
-            emitMove(m_newTargetRegister, &m_thisRegister);
             emitMoveEmptyValue(&m_thisRegister);
         } else
             emitCreateThis(&m_thisRegister);
index 27a8468..b2f7350 100644 (file)
@@ -185,6 +185,16 @@ static RegisterID* emitSuperBaseForCallee(BytecodeGenerator& generator)
     return generator.emitGetById(generator.newTemporary(), homeObject.get(), generator.propertyNames().underscoreProto);
 }
 
+// ------------------------------ NewTargetNode ----------------------------------
+
+RegisterID* NewTargetNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
+{
+    if (dst == generator.ignoredResult())
+        return nullptr;
+
+    return generator.moveToDestinationIfNeeded(dst, generator.newTarget());
+}
+
 // ------------------------------ ResolveNode ----------------------------------
 
 bool ResolveNode::isPure(BytecodeGenerator& generator) const
index b0c9978..c1c7625 100644 (file)
@@ -177,6 +177,10 @@ public:
     {
         return new (m_parserArena) SuperNode(location);
     }
+    ExpressionNode* newTargetExpr(const JSTokenLocation location)
+    {
+        return new (m_parserArena) NewTargetNode(location);
+    }
     ExpressionNode* createResolve(const JSTokenLocation& location, const Identifier* ident, const JSTextPosition& start)
     {
         if (m_vm->propertyNames->arguments == *ident)
index 1167e82..6cd7627 100644 (file)
@@ -170,6 +170,11 @@ namespace JSC {
     {
     }
 
+    inline NewTargetNode::NewTargetNode(const JSTokenLocation& location)
+        : ExpressionNode(location)
+    {
+    }
+
     inline ResolveNode::ResolveNode(const JSTokenLocation& location, const Identifier& ident, const JSTextPosition& start)
         : ExpressionNode(location)
         , m_ident(ident)
index 7265c8c..cc3bfc5 100644 (file)
@@ -547,6 +547,14 @@ namespace JSC {
         virtual RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
     };
 
+    class NewTargetNode final : public ExpressionNode {
+    public:
+        NewTargetNode(const JSTokenLocation&);
+
+    private:
+        virtual RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
+    };
+
     class ResolveNode : public ExpressionNode {
     public:
         ResolveNode(const JSTokenLocation&, const Identifier&, const JSTextPosition& start);
index 9511c17..5c3e815 100644 (file)
@@ -2883,11 +2883,28 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseMemberExpres
     bool baseIsSuper = false;
 #endif
 
+    bool baseIsNewTarget = false;
+    if (newCount && match(DOT)) {
+        next();
+        if (match(IDENT)) {
+            const Identifier* ident = m_token.m_data.ident;
+            if (m_vm->propertyNames->target == *ident) {
+                semanticFailIfFalse(currentScope()->isFunction(), "new.target is only valid inside functions");
+                baseIsNewTarget = true;
+                base = context.newTargetExpr(location);
+                newCount--;
+                next();
+            } else
+                failWithMessage("\"new.\" can only followed with target");
+        } else
+            failDueToUnexpectedToken();
+    }
+
     if (baseIsSuper) {
         base = context.superExpr(location);
         next();
         currentScope()->setNeedsSuperBinding();
-    } else
+    } else if (!baseIsNewTarget)
         base = parsePrimaryExpression(context);
 
     failIfFalse(base, "Cannot parse base expression");
index ee0e558..3951b8b 100644 (file)
@@ -74,7 +74,7 @@ public:
         ThisExpr, NullExpr, BoolExpr, RegExpExpr, ObjectLiteralExpr,
         FunctionExpr, ClassExpr, SuperExpr, BracketExpr, DotExpr, CallExpr,
         NewExpr, PreExpr, PostExpr, UnaryExpr, BinaryExpr,
-        ConditionalExpr, AssignmentExpr, TypeofExpr,
+        ConditionalExpr, AssignmentExpr, TypeofExpr, NewTargetExpr,
         DeleteExpr, ArrayLiteralExpr, BindingDestructuring,
         ArrayDestructuring, ObjectDestructuring, SourceElementsResult,
         FunctionBodyResult, SpreadExpr, ArgumentsResult,
@@ -154,6 +154,7 @@ public:
     ExpressionType createVoid(const JSTokenLocation&, ExpressionType) { return UnaryExpr; }
     ExpressionType thisExpr(const JSTokenLocation&, ThisTDZMode) { return ThisExpr; }
     ExpressionType superExpr(const JSTokenLocation&) { return SuperExpr; }
+    ExpressionType newTargetExpr(const JSTokenLocation&) { return NewTargetExpr; }
     ExpressionType createResolve(const JSTokenLocation&, const Identifier*, int) { return ResolveExpr; }
     ExpressionType createObjectLiteral(const JSTokenLocation&) { return ObjectLiteralExpr; }
     ExpressionType createObjectLiteral(const JSTokenLocation&, int) { return ObjectLiteralExpr; }
index 41b94f1..fda5289 100644 (file)
     macro(sourceCode) \
     macro(stack) \
     macro(subarray) \
+    macro(target) \
     macro(test) \
     macro(then) \
     macro(toExponential) \
diff --git a/Source/JavaScriptCore/tests/stress/new-target.js b/Source/JavaScriptCore/tests/stress/new-target.js
new file mode 100644 (file)
index 0000000..d7a83ff
--- /dev/null
@@ -0,0 +1,79 @@
+var passed = true;
+try {
+    eval("new.target;");
+    passed = false;
+} catch(e) {}
+
+test(passed, true, "new.target cannot be called in global scope");
+
+// Test without class syntax
+
+function test(result, expected, message) {
+    if (result !== expected)
+        throw "Error: " + message + ". was: " + result + " wanted: " + expected;
+}
+
+function call() {
+    test(new.target, undefined, "new.target should be undefined in a function call");
+}
+call();
+
+function Constructor() {
+    test(new.target, Constructor, "new.target should be the same as constructor");
+    function subCall() {
+        test(new.target, undefined, "new.target should be undefined in a sub function call");
+    }
+    subCall();
+
+    function SubConstructor() {
+        test(new.target, SubConstructor, "new.target should be subConstructor");
+    }
+    new SubConstructor();
+
+}
+new Constructor();
+
+function noAssign() {
+    new.target = 1;
+}
+
+try {
+    new noAssign();
+    passed = false;
+} catch(e) { }
+try {
+    noAssign();
+    passed = false;
+} catch(e) { }
+
+test(passed, true, "new.target should not be a reference");
+
+// This is mostly to test that calling new on new.target deos the right thing.
+function doWeirdThings(arg) {
+    if (new.target) {
+        if (arg)
+            this.value = new.target(1);
+        else
+            this.value = new new.target(true);
+    } else
+        return arg;
+}
+
+test(new doWeirdThings(false).value.value, 1, "calling new on new.target did something weird");
+
+// Test with class syntax
+
+class SuperClass {
+    constructor() {
+        this.target = new.target;
+    }
+}
+
+class SubClass extends SuperClass {
+    constructor() {
+        super();
+    }
+}
+
+test(new SuperClass().target, SuperClass, "new.target should be the same as the class constructor");
+test(new SubClass().target, SubClass, "new.target should not change when passed through super()");