From 0fef518cbecfb9fd5fdd9703216b0dec184975f9 Mon Sep 17 00:00:00 2001 From: uchida_t Date: Wed, 19 Aug 2015 15:54:39 +0900 Subject: [PATCH] Support Computed Property Name * Invalid evaluating in order * Not support destructing assignment * When computed property set to `__proto__`, [[Prototype]] changed. --- src/org/mozilla/javascript/CodeGenerator.java | 24 +++++- src/org/mozilla/javascript/IRFactory.java | 13 ++- src/org/mozilla/javascript/Icode.java | 8 +- src/org/mozilla/javascript/Interpreter.java | 20 ++++- .../mozilla/javascript/NodeTransformer.java | 12 +++ src/org/mozilla/javascript/Parser.java | 82 +++++++++++------- src/org/mozilla/javascript/ScriptRuntime.java | 9 ++ src/org/mozilla/javascript/Token.java | 1 + .../javascript/ast/ObjectProperty.java | 16 ++++ .../mozilla/javascript/optimizer/Codegen.java | 16 +++- .../jstests/harmony/computed-property-name.js | 83 +++++++++++++++++++ .../javascript/drivers/JsTestsBase.java | 2 +- .../harmony/ComputedPropertyNameTest.java | 16 ++++ testsrc/test262.properties | 24 +++++- 14 files changed, 286 insertions(+), 40 deletions(-) create mode 100644 testsrc/jstests/harmony/computed-property-name.js create mode 100644 testsrc/org/mozilla/javascript/tests/harmony/ComputedPropertyNameTest.java diff --git a/src/org/mozilla/javascript/CodeGenerator.java b/src/org/mozilla/javascript/CodeGenerator.java index 423bc38d07..46f5951cd5 100644 --- a/src/org/mozilla/javascript/CodeGenerator.java +++ b/src/org/mozilla/javascript/CodeGenerator.java @@ -1082,9 +1082,30 @@ private void visitLiteral(Node node, Node child) } else { throw badTree(node); } + if (type == Token.OBJECTLIT) { + addIndexOp(Icode_LITERAL_KEY_NEW, count); + stackChange(1); + } addIndexOp(Icode_LITERAL_NEW, count); stackChange(2); + int i = 0; while (child != null) { + if (type == Token.OBJECTLIT) { + Object id = propertyIds[i++]; + if (id instanceof String) { + addStringOp(Token.STRING, (String)id); + stackChange(1); + } else if (id instanceof Number) { + int index = getDoubleIndex(((Number)id).doubleValue()); + addIndexOp(Token.NUMBER, index); + stackChange(1); + } else { + visitExpression((Node)id, 0); + } + addIcode(Icode_LITERAL_KEY_SET); + stackChange(-1); + } + int childType = child.getType(); if (childType == Token.GET) { visitExpression(child.getFirstChild(), 0); @@ -1111,12 +1132,13 @@ private void visitLiteral(Node node, Node child) literalIds.add(skipIndexes); addIndexOp(Icode_SPARE_ARRAYLIT, index); } + stackChange(-1); } else { int index = literalIds.size(); literalIds.add(propertyIds); addIndexOp(Token.OBJECTLIT, index); + stackChange(-2); } - stackChange(-1); } private void visitArrayComprehension(Node node, Node initStmt, Node expr) diff --git a/src/org/mozilla/javascript/IRFactory.java b/src/org/mozilla/javascript/IRFactory.java index 684a549bc7..9f0f71d5b3 100644 --- a/src/org/mozilla/javascript/IRFactory.java +++ b/src/org/mozilla/javascript/IRFactory.java @@ -875,8 +875,18 @@ private Node transformObjectLiteral(ObjectLiteral node) { } else if (prop.isNormalMethod()) { decompiler.addToken(Token.METHOD); } + if (prop.isComputed()) { + decompiler.addToken(Token.LB); + } - properties[i++] = getPropKey(prop.getLeft()); + if (prop.isComputed()) { + properties[i++] = transform(prop.getLeft()); + } else { + properties[i++] = getPropKey(prop.getLeft()); + } + if (prop.isComputed()) { + decompiler.addToken(Token.RB); + } // OBJECTLIT is used as ':' in object literal for // decompilation to solve spacing ambiguity. @@ -900,6 +910,7 @@ private Node transformObjectLiteral(ObjectLiteral node) { } } decompiler.addToken(Token.RC); + // TODO : add Node tree for computed property name. object.putProp(Node.OBJECT_IDS_PROP, properties); return object; } diff --git a/src/org/mozilla/javascript/Icode.java b/src/org/mozilla/javascript/Icode.java index f454ca680c..0116803ae5 100644 --- a/src/org/mozilla/javascript/Icode.java +++ b/src/org/mozilla/javascript/Icode.java @@ -135,8 +135,12 @@ abstract class Icode { Icode_DEBUGGER = -64, + // Object Literal Key (for Computed property name) + Icode_LITERAL_KEY_NEW = -65, + Icode_LITERAL_KEY_SET = -66, + // Last icode - MIN_ICODE = -64; + MIN_ICODE = -66; static String bytecodeName(int bytecode) { @@ -217,6 +221,8 @@ static String bytecodeName(int bytecode) case Icode_GENERATOR: return "GENERATOR"; case Icode_GENERATOR_END: return "GENERATOR_END"; case Icode_DEBUGGER: return "DEBUGGER"; + case Icode_LITERAL_KEY_NEW: return "LITERAL_KEY_NEW"; + case Icode_LITERAL_KEY_SET: return "LITERAL_KEY_SET"; } // icode without name diff --git a/src/org/mozilla/javascript/Interpreter.java b/src/org/mozilla/javascript/Interpreter.java index c591b1c8c1..1f4eaa6828 100644 --- a/src/org/mozilla/javascript/Interpreter.java +++ b/src/org/mozilla/javascript/Interpreter.java @@ -1765,15 +1765,33 @@ private static Object interpretLoop(Context cx, CallFrame frame, sDbl[stackTop] = i + 1; continue Loop; } + case Icode_LITERAL_KEY_NEW: { + ++stackTop; + stack[stackTop] = new Object[indexReg]; + sDbl[stackTop] = 0; + continue Loop; + } + case Icode_LITERAL_KEY_SET : { + Object value = stack[stackTop]; + if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + int i = (int)sDbl[stackTop]; + ((Object[])stack[stackTop - 2])[i] = ScriptRuntime.toPropertyKey(value, cx); + continue Loop; + } case Token.ARRAYLIT : case Icode_SPARE_ARRAYLIT : case Token.OBJECTLIT : { Object[] data = (Object[])stack[stackTop]; --stackTop; int[] getterSetters = (int[])stack[stackTop]; + Object[] ids = null; + if (op == Token.OBJECTLIT) { + --stackTop; + ids = (Object[])stack[stackTop]; + } Object val; if (op == Token.OBJECTLIT) { - Object[] ids = (Object[])frame.idata.literalIds[indexReg]; val = ScriptRuntime.newObjectLiteral(ids, data, getterSetters, cx, frame.scope); } else { diff --git a/src/org/mozilla/javascript/NodeTransformer.java b/src/org/mozilla/javascript/NodeTransformer.java index 6e0a5cd52d..6c31788ad3 100644 --- a/src/org/mozilla/javascript/NodeTransformer.java +++ b/src/org/mozilla/javascript/NodeTransformer.java @@ -394,6 +394,18 @@ else if (last.getType() == Token.NAME && } break; } + + case Token.OBJECTLIT: { + Object[] properties = (Object[])node.getProp(Node.OBJECT_IDS_PROP); + for (Object prop : properties) { + if (prop instanceof Node) { + transformCompilationUnit_r(tree, (Node)prop, scope, + createScopeObjects, + inStrictMode); + } + } + break; + } } transformCompilationUnit_r(tree, node, diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java index 2ca9c79675..9d3805c558 100644 --- a/src/org/mozilla/javascript/Parser.java +++ b/src/org/mozilla/javascript/Parser.java @@ -3327,10 +3327,9 @@ private GeneratorExpressionLoop generatorExpressionLoop() } } - private static final int PROP_ENTRY = 1; - private static final int GET_ENTRY = 2; - private static final int SET_ENTRY = 4; - private static final int METHOD_ENTRY = 8; + private enum PropertyEntryKind { + LITERAL, GET, SET, METHOD + } private ObjectLiteral objectLiteral() throws IOException @@ -3349,7 +3348,7 @@ private ObjectLiteral objectLiteral() commaLoop: for (;;) { String propertyName = null; - int entryKind = PROP_ENTRY; + PropertyEntryKind entryKind = PropertyEntryKind.LITERAL; int tt = peekToken(); Comment jsdocNode = getAndResetJsDoc(); @@ -3358,7 +3357,7 @@ private ObjectLiteral objectLiteral() warnTrailingComma(pos, elems, afterComma); break commaLoop; } else { - AstNode pname = objliteralProperty(); + ObjectPropertyName pname = objliteralProperty(); if (pname == null) { propertyName = null; reportError("msg.bad.prop"); @@ -3381,15 +3380,15 @@ private ObjectLiteral objectLiteral() && peeked != Token.RC) { if (peeked == Token.LP) { - entryKind = METHOD_ENTRY; - } else if (pname.getType() == Token.NAME) { + entryKind = PropertyEntryKind.METHOD; + } else if (!pname.computed && pname.name.getType() == Token.NAME) { if ("get".equals(propertyName)) { - entryKind = GET_ENTRY; + entryKind = PropertyEntryKind.GET; } else if ("set".equals(propertyName)) { - entryKind = SET_ENTRY; + entryKind = PropertyEntryKind.SET; } } - if (entryKind == GET_ENTRY || entryKind == SET_ENTRY) { + if (entryKind == PropertyEntryKind.GET || entryKind == PropertyEntryKind.SET) { pname = objliteralProperty(); if (pname == null) { reportError("msg.bad.prop"); @@ -3401,21 +3400,24 @@ private ObjectLiteral objectLiteral() } else { propertyName = ts.getString(); ObjectProperty objectProp = methodDefinition( - ppos, pname, entryKind); - pname.setJsDocNode(jsdocNode); + ppos, pname.name, entryKind); + pname.name.setJsDocNode(jsdocNode); + objectProp.setComputed(pname.computed); elems.add(objectProp); } } else { - pname.setJsDocNode(jsdocNode); - elems.add(plainProperty(pname, tt)); + pname.name.setJsDocNode(jsdocNode); + ObjectProperty objectProp = plainProperty(pname.name, tt); + objectProp.setComputed(pname.computed); + elems.add(objectProp); } } } if (this.inUseStrictDirective && propertyName != null) { switch (entryKind) { - case PROP_ENTRY: - case METHOD_ENTRY: + case LITERAL: + case METHOD: if (getterNames.contains(propertyName) || setterNames.contains(propertyName)) { addError("msg.dup.obj.lit.prop.strict", propertyName); @@ -3423,13 +3425,13 @@ private ObjectLiteral objectLiteral() getterNames.add(propertyName); setterNames.add(propertyName); break; - case GET_ENTRY: + case GET: if (getterNames.contains(propertyName)) { addError("msg.dup.obj.lit.prop.strict", propertyName); } getterNames.add(propertyName); break; - case SET_ENTRY: + case SET: if (setterNames.contains(propertyName)) { addError("msg.dup.obj.lit.prop.strict", propertyName); } @@ -3458,34 +3460,54 @@ private ObjectLiteral objectLiteral() return pn; } - private AstNode objliteralProperty() throws IOException { - AstNode pname; + private static class ObjectPropertyName { + AstNode name; + boolean computed; + + public ObjectPropertyName(AstNode name, boolean computed) { + this.name = name; + this.computed = computed; + } + } + + private ObjectPropertyName objliteralProperty() throws IOException { + AstNode name; + boolean computed = false; int tt = peekToken(); switch(tt) { case Token.NAME: - pname = createNameNode(); + name = createNameNode(); break; case Token.STRING: - pname = createStringLiteral(); + name = createStringLiteral(); break; case Token.NUMBER: - pname = new NumberLiteral( + name = new NumberLiteral( ts.tokenBeg, ts.getString(), ts.getNumber()); break; + case Token.LB: + if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + computed = true; + consumeToken(); + name = assignExpr(); + mustMatchToken(Token.RB, "msg.no.bracket.arg"); + break; + } + default: if (compilerEnv.isReservedKeywordAsIdentifier() && TokenStream.isKeyword(ts.getString())) { // convert keyword to property name, e.g. ({if: 1}) - pname = createNameNode(); + name = createNameNode(); break; } return null; } - return pname; + return new ObjectPropertyName(name, computed); } private ObjectProperty plainProperty(AstNode property, int ptt) @@ -3512,7 +3534,7 @@ private ObjectProperty plainProperty(AstNode property, int ptt) return pn; } - private ObjectProperty methodDefinition(int pos, AstNode propName, int entryKind) + private ObjectProperty methodDefinition(int pos, AstNode propName, PropertyEntryKind entryKind) throws IOException { FunctionNode fn = function(FunctionNode.FUNCTION_EXPRESSION); @@ -3523,15 +3545,15 @@ private ObjectProperty methodDefinition(int pos, AstNode propName, int entryKind } ObjectProperty pn = new ObjectProperty(pos); switch (entryKind) { - case GET_ENTRY: + case GET: pn.setIsGetterMethod(); fn.setFunctionIsGetterMethod(); break; - case SET_ENTRY: + case SET: pn.setIsSetterMethod(); fn.setFunctionIsSetterMethod(); break; - case METHOD_ENTRY: + case METHOD: pn.setIsNormalMethod(); fn.setFunctionIsNormalMethod(); break; diff --git a/src/org/mozilla/javascript/ScriptRuntime.java b/src/org/mozilla/javascript/ScriptRuntime.java index 0e6549af0a..c02633a4ae 100644 --- a/src/org/mozilla/javascript/ScriptRuntime.java +++ b/src/org/mozilla/javascript/ScriptRuntime.java @@ -1443,6 +1443,15 @@ static String toStringIdOrIndex(Context cx, Object id) } } + public static Object toPropertyKey(Object elem, Context cx) { + String s = toStringIdOrIndex(cx, elem); + if (s != null) { + return s; + } + return lastIndexResult(cx); + } + + /** * Call obj.[[Get]](id) * diff --git a/src/org/mozilla/javascript/Token.java b/src/org/mozilla/javascript/Token.java index cf6c5f9604..45592623a5 100644 --- a/src/org/mozilla/javascript/Token.java +++ b/src/org/mozilla/javascript/Token.java @@ -418,6 +418,7 @@ public static String typeToName(int token) { case COMMENT: return "COMMENT"; case GENEXPR: return "GENEXPR"; case METHOD: return "METHOD"; + case ARROW: return "ARROW"; } // Token without name diff --git a/src/org/mozilla/javascript/ast/ObjectProperty.java b/src/org/mozilla/javascript/ast/ObjectProperty.java index 7fc347be47..7ae1851698 100644 --- a/src/org/mozilla/javascript/ast/ObjectProperty.java +++ b/src/org/mozilla/javascript/ast/ObjectProperty.java @@ -102,6 +102,14 @@ public boolean isMethod() { return isGetterMethod() || isSetterMethod() || isNormalMethod(); } + public boolean isComputed() { + return computed; + } + + public void setComputed(boolean computed) { + this.computed = computed; + } + @Override public String toSource(int depth) { StringBuilder sb = new StringBuilder(); @@ -112,11 +120,19 @@ public String toSource(int depth) { } else if (isSetterMethod()) { sb.append("set "); } + if (computed) { + sb.append("["); + } sb.append(left.toSource(getType()==Token.COLON ? 0 : depth)); + if (computed) { + sb.append("]"); + } if (type == Token.COLON) { sb.append(": "); } sb.append(right.toSource(getType()==Token.COLON ? 0 : depth+1)); return sb.toString(); } + + private boolean computed; } diff --git a/src/org/mozilla/javascript/optimizer/Codegen.java b/src/org/mozilla/javascript/optimizer/Codegen.java index 3d3c8033b7..488bbd8a42 100644 --- a/src/org/mozilla/javascript/optimizer/Codegen.java +++ b/src/org/mozilla/javascript/optimizer/Codegen.java @@ -3158,7 +3158,7 @@ private void visitArrayLiteral(Node node, Node child, boolean topLevel) } /** load array with property ids */ - private void addLoadPropertyIds(Object[] properties, int count) { + private void addLoadPropertyIds(Node node, Object[] properties, int count) { addNewObjectArray(count); for (int i = 0; i != count; ++i) { cfw.add(ByteCode.DUP); @@ -3166,9 +3166,17 @@ private void addLoadPropertyIds(Object[] properties, int count) { Object id = properties[i]; if (id instanceof String) { cfw.addPush((String)id); - } else { + } else if (id instanceof Number) { cfw.addPush(((Integer)id).intValue()); addScriptRuntimeInvoke("wrapInt", "(I)Ljava/lang/Integer;"); + } else { + generateExpression((Node)id, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "toPropertyKey", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); } cfw.add(ByteCode.AASTORE); } @@ -3244,11 +3252,11 @@ private void visitObjectLiteral(Node node, Node child, boolean topLevel) // TODO: this is actually only necessary if the yield operation is // a child of this object or its children (bug 757410) addLoadPropertyValues(node, child, count); - addLoadPropertyIds(properties, count); + addLoadPropertyIds(node, properties, count); // swap property-values and property-ids arrays cfw.add(ByteCode.SWAP); } else { - addLoadPropertyIds(properties, count); + addLoadPropertyIds(node, properties, count); addLoadPropertyValues(node, child, count); } diff --git a/testsrc/jstests/harmony/computed-property-name.js b/testsrc/jstests/harmony/computed-property-name.js new file mode 100644 index 0000000000..5ecf25ac8b --- /dev/null +++ b/testsrc/jstests/harmony/computed-property-name.js @@ -0,0 +1,83 @@ + +load("testsrc/assert.js"); + +var a = { + [1+2]: 'prop1', + ['foo' + 'bar']: 'prop2', + [a]: 'prop3', + [{}]: 'prop4', + [null]: 'prop5' +}; +assertEquals('prop1', a[3]); +assertEquals('prop2', a.foobar); +assertEquals('prop3', a.undefined); +assertEquals('prop4', a['[object Object]']); +assertEquals('prop5', a.null); + +assertThrows('var b = { []: };', SyntaxError); +assertThrows('var c = { [1, 2]: };', SyntaxError); + +assertEquals('\n' + +'function () {\n' + +' return {[a + 123]: 456};\n' + +'}\n', function () { + return {[a + 123]: 456}; +}.toString()); + +var d = 0; +var e = { + [d++]: d++, + [d++]: d++, + [d++]: d++ +}; +assertEquals(1, e[0]); +assertEquals(3, e[2]); +assertEquals(5, e[4]); + +var f = 'foo'; +var g = { + toString: function() { + return f; + } +}; +var h = { + [g]: (function() { + f = 'bar'; + return 'abc'; + })(), + [g]: 'efg' +}; + +assertEquals('abc', h.foo); +assertEquals('efg', h.bar); +/* +var i = 'abc'; +var { [i]: j } = { abc: 123 }; + +assertEquals('123', j); + +var k = { + ['__proto__']: { abc: 123 } +}; +assertEquals(undefined, k.abc); +*/ +// tight swap out literals +var l = (function(n) { + return { + 'key0': 'value0', + 'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3', + 'key4': 'value4', + 'key5': 'value5', + 'key6': 'value6', + 'key7': 'value7', + 'key8': 'value8', + 'key9': 'value9', + ['key' + n]: 'value10' + }; +})(10); + +assertEquals('value10', l.key10); + +"success" diff --git a/testsrc/org/mozilla/javascript/drivers/JsTestsBase.java b/testsrc/org/mozilla/javascript/drivers/JsTestsBase.java index f628e256f2..2f6faacd58 100644 --- a/testsrc/org/mozilla/javascript/drivers/JsTestsBase.java +++ b/testsrc/org/mozilla/javascript/drivers/JsTestsBase.java @@ -28,7 +28,7 @@ public void runJsTest(Context cx, Scriptable shared, String name, String source) System.out.print(name + ": "); Object result; try { - result = cx.evaluateString(scope, source, "jstest input", 1, null); + result = cx.evaluateString(scope, source, "jstest input: " + name, 1, null); } catch (RuntimeException e) { e.printStackTrace(System.err); System.out.println("FAILED"); diff --git a/testsrc/org/mozilla/javascript/tests/harmony/ComputedPropertyNameTest.java b/testsrc/org/mozilla/javascript/tests/harmony/ComputedPropertyNameTest.java new file mode 100644 index 0000000000..3b6664c471 --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/harmony/ComputedPropertyNameTest.java @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript.tests.harmony; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.drivers.LanguageVersion; +import org.mozilla.javascript.drivers.RhinoTest; +import org.mozilla.javascript.drivers.ScriptTestsBase; + +@RhinoTest("testsrc/jstests/harmony/computed-property-name.js") +@LanguageVersion(Context.VERSION_ES6) +public class ComputedPropertyNameTest extends ScriptTestsBase +{ +} diff --git a/testsrc/test262.properties b/testsrc/test262.properties index 0804742dec..866162bf1b 100644 --- a/testsrc/test262.properties +++ b/testsrc/test262.properties @@ -262,4 +262,26 @@ language/expressions/delete/11.4.1-4.a-9.js language/expressions/delete/11.4.4-4.a-3-s.js language/types/reference/8.7.2-3-s.js language/types/reference/8.7.2-4-s.js -language/types/reference/8.7.2-7-s.js \ No newline at end of file +language/types/reference/8.7.2-7-s.js + +language/computed-property-names +# ignore key order + ! basics/number.js + ! object/method/number.js + ! to-name-side-effects/numbers-object.js +# `Symbol` is not implemented + ! basics/symbol.js + ! object/method/symbol.js +# duplicate property name is valid in strict mode. + ! object/accessor/getter-duplicates.js + ! object/accessor/setter-duplicates.js +# `Class` is not implemented + ! /class/ + ! object/accessor/getter-super.js + ! object/accessor/setter-super.js + ! object/method/super.js + ! to-name-side-effects/class.js + ! to-name-side-effects/numbers-class.js +# ES6's `Generator` is not implemented + ! object/method/generator.js + ! object/property/number-duplicates.js