From ab81d2e2615ec36a049440a76a83eee9f8040337 Mon Sep 17 00:00:00 2001 From: Peter Rybin Date: Mon, 14 Apr 2014 04:13:21 +0400 Subject: [PATCH] Supporting data class for JS backend --- .../k2js/test/semantics/DataClassTest.java | 49 +++++ .../declaration/ClassTranslator.java | 8 +- .../declaration/JsDataClassGenerator.java | 192 ++++++++++++++++++ .../functions/factories/TopLevelFIF.java | 7 + .../k2js/translate/utils/JsAstUtils.java | 5 + .../testData/dataClass/cases/components.kt | 24 +++ .../testData/dataClass/cases/copy.kt | 11 + .../testData/dataClass/cases/equals.kt | 49 +++++ .../testData/dataClass/cases/hashcode.kt | 88 ++++++++ .../testData/dataClass/cases/keyrole.kt | 27 +++ .../testData/dataClass/cases/tostring.kt | 27 +++ js/js.translator/testData/kotlin_lib.js | 44 ++++ js/js.translator/testData/maps.js | 10 +- 13 files changed, 534 insertions(+), 7 deletions(-) create mode 100644 js/js.tests/test/org/jetbrains/k2js/test/semantics/DataClassTest.java create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/declaration/JsDataClassGenerator.java create mode 100644 js/js.translator/testData/dataClass/cases/components.kt create mode 100644 js/js.translator/testData/dataClass/cases/copy.kt create mode 100644 js/js.translator/testData/dataClass/cases/equals.kt create mode 100644 js/js.translator/testData/dataClass/cases/hashcode.kt create mode 100644 js/js.translator/testData/dataClass/cases/keyrole.kt create mode 100644 js/js.translator/testData/dataClass/cases/tostring.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/DataClassTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/DataClassTest.java new file mode 100644 index 00000000000..c807d4d98ee --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/DataClassTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.test.semantics; + +import org.jetbrains.k2js.test.SingleFileTranslationTest; + +public class DataClassTest extends SingleFileTranslationTest { + public DataClassTest() { + super("dataClass/"); + } + + public void testComponents() throws Exception { + checkFooBoxIsOk(); + } + + public void testCopy() throws Exception { + checkFooBoxIsOk(); + } + + public void testEquals() throws Exception { + checkFooBoxIsOk(); + } + + public void testHashcode() throws Exception { + checkFooBoxIsOk(); + } + + public void testTostring() throws Exception { + checkFooBoxIsOk(); + } + + public void testKeyrole() throws Exception { + checkFooBoxIsOk(); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java index 168a7f16a1c..1264a906422 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java @@ -28,6 +28,7 @@ import org.jetbrains.jet.lang.psi.JetObjectDeclaration; import org.jetbrains.jet.lang.psi.JetParameter; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.TypeConstructor; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.k2js.translate.context.DefinitionPlace; import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.TranslationContext; @@ -45,8 +46,7 @@ import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.transla import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor; import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter; import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass; -import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration; -import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getSupertypesWithoutFakes; +import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*; import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters; import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction; @@ -134,6 +134,10 @@ public final class ClassTranslator extends AbstractTranslator { bodyVisitor.traverseContainer(classDeclaration, declarationContext); mayBeAddEnumEntry(bodyVisitor.getEnumEntryList(), staticProperties, declarationContext); + if (KotlinBuiltIns.getInstance().isData(descriptor)) { + new JsDataClassGenerator(classDeclaration, declarationContext, properties).generate(); + } + boolean hasStaticProperties = !staticProperties.isEmpty(); if (!properties.isEmpty() || hasStaticProperties) { if (properties.isEmpty()) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/JsDataClassGenerator.java b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/JsDataClassGenerator.java new file mode 100644 index 00000000000..51435b9f982 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/JsDataClassGenerator.java @@ -0,0 +1,192 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.translate.declaration; + +import com.google.dart.compiler.backend.js.ast.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.backend.common.CodegenUtil; +import org.jetbrains.jet.backend.common.DataClassMethodGenerator; +import org.jetbrains.jet.lang.descriptors.*; +import org.jetbrains.jet.lang.psi.JetClassOrObject; +import org.jetbrains.jet.lang.psi.JetParameter; +import org.jetbrains.k2js.translate.context.Namer; +import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.utils.JsAstUtils; + +import java.util.ArrayList; +import java.util.List; + +class JsDataClassGenerator extends DataClassMethodGenerator { + private final TranslationContext context; + private final List output; + + JsDataClassGenerator(JetClassOrObject klass, TranslationContext context, List output) { + super(klass, context.bindingContext()); + this.context = context; + this.output = output; + } + + @Override + public void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter) { + JsFunction functionObject = generateJsMethod(function); + JsExpression returnExpression = propertyAccessor(JsLiteral.THIS, context.getNameForDescriptor(parameter).toString()); + functionObject.getBody().getStatements().add(new JsReturn(returnExpression)); + } + + @Override + public void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List constructorParameters) { + JsFunction functionObj = generateJsMethod(function); + JsScope funScope = functionObj.getScope(); + + assert function.getValueParameters().size() == constructorParameters.size(); + + List constructorArguments = new ArrayList(constructorParameters.size()); + for (int i = 0; i < constructorParameters.size(); i++) { + JetParameter constructorParam = constructorParameters.get(i); + JsName paramName = funScope.declareName(constructorParam.getName()); + + functionObj.getParameters().add(new JsParameter(paramName)); + + JsExpression argumentValue; + JsExpression parameterValue = new JsNameRef(paramName); + if (constructorParam.getValOrVarNode() == null) { + assert !function.getValueParameters().get(i).hasDefaultValue(); + // Caller cannot rely on default value and pass undefined here. + argumentValue = parameterValue; + } + else { + JsExpression defaultCondition = JsAstUtils.equality(new JsNameRef(paramName), context.namer().getUndefinedExpression()); + argumentValue = new JsConditional(defaultCondition, + propertyAccessor(JsLiteral.THIS, constructorParam.getName()), + parameterValue); + } + constructorArguments.add(argumentValue); + } + + ClassDescriptor classDescriptor = (ClassDescriptor) function.getContainingDeclaration(); + ConstructorDescriptor constructor = classDescriptor.getConstructors().iterator().next(); + + JsExpression constructorRef = context.getQualifiedReference(constructor); + + JsExpression returnExpression = new JsNew(constructorRef, constructorArguments); + functionObj.getBody().getStatements().add(new JsReturn(returnExpression)); + } + + @Override + public void generateToStringMethod(@NotNull List classProperties) { + // TODO: relax this limitation, with the data generation logic fixed. + assert !classProperties.isEmpty(); + FunctionDescriptor prototypeFun = CodegenUtil.getAnyToStringMethod(); + JsFunction functionObj = generateJsMethod(prototypeFun); + + JsProgram jsProgram = context.program(); + JsExpression result = null; + for (int i = 0; i < classProperties.size(); i++) { + String name = classProperties.get(i).getName().toString(); + JsExpression literal = jsProgram.getStringLiteral((i == 0 ? (getClassDescriptor().getName() + "(") : ", ") + name + "="); + JsExpression expr = + new JsInvocation(new JsNameRef("toString", Namer.KOTLIN_OBJECT_REF), propertyAccessor(JsLiteral.THIS, name)); + JsExpression component = JsAstUtils.sum(literal, expr); + if (result == null) { + result = component; + } + else { + result = JsAstUtils.sum(result, component); + } + } + assert result != null; + result = JsAstUtils.sum(result, jsProgram.getStringLiteral(")")); + functionObj.getBody().getStatements().add(new JsReturn(result)); + } + + @Override + public void generateHashCodeMethod(@NotNull List classProperties) { + FunctionDescriptor prototypeFun = CodegenUtil.getAnyHashCodeMethod(); + JsFunction functionObj = generateJsMethod(prototypeFun); + + JsProgram jsProgram = context.program(); + List statements = functionObj.getBody().getStatements(); + + JsName varName = functionObj.getScope().declareName("result"); + + int typeHash = context.getQualifiedReference(getClassDescriptor()).toString().hashCode(); + statements.add(new JsVars(new JsVars.JsVar(varName, jsProgram.getNumberLiteral(typeHash)))); + + for (PropertyDescriptor prop : classProperties) { + // TODO: we should statically check that we can call hashCode method directly. + JsExpression component = new JsInvocation(new JsNameRef("hashCode", Namer.KOTLIN_OBJECT_REF), + propertyAccessor(JsLiteral.THIS, prop.getName().toString())); + JsExpression newHashValue = JsAstUtils.sum(JsAstUtils.mul(new JsNameRef(varName), jsProgram.getNumberLiteral(31)), component); + JsExpression assignment = JsAstUtils.assignment(new JsNameRef(varName), + new JsBinaryOperation(JsBinaryOperator.BIT_OR, newHashValue, + jsProgram.getNumberLiteral(0))); + statements.add(assignment.makeStmt()); + } + + statements.add(new JsReturn(new JsNameRef(varName))); + } + + @Override + public void generateEqualsMethod(@NotNull List classProperties) { + assert !classProperties.isEmpty(); + FunctionDescriptor prototypeFun = CodegenUtil.getAnyEqualsMethod(); + JsFunction functionObj = generateJsMethod(prototypeFun); + JsScope funScope = functionObj.getScope(); + + JsName paramName = funScope.declareName("other"); + functionObj.getParameters().add(new JsParameter(paramName)); + + JsExpression referenceEqual = JsAstUtils.equality(JsLiteral.THIS, new JsNameRef(paramName)); + JsExpression isNotNull = JsAstUtils.inequality(new JsNameRef(paramName), JsLiteral.NULL); + JsExpression prototypeEqual = + JsAstUtils.equality(new JsInvocation(new JsNameRef("getPrototypeOf", new JsNameRef("Object")), JsLiteral.THIS), + new JsInvocation(new JsNameRef("getPrototypeOf", new JsNameRef("Object")), new JsNameRef(paramName))); + + JsExpression fieldChain = null; + for (PropertyDescriptor prop : classProperties) { + String name = prop.getName().toString(); + JsExpression next = new JsInvocation(new JsNameRef("equals", Namer.KOTLIN_OBJECT_REF), + propertyAccessor(JsLiteral.THIS, name), + propertyAccessor(new JsNameRef(paramName), name)); + if (fieldChain == null) { + fieldChain = next; + } + else { + fieldChain = JsAstUtils.and(fieldChain, next); + } + } + assert fieldChain != null; + + JsExpression returnExpression = + JsAstUtils.or(referenceEqual, JsAstUtils.and(isNotNull, JsAstUtils.and(prototypeEqual, fieldChain))); + functionObj.getBody().getStatements().add(new JsReturn(returnExpression)); + } + + private static JsExpression propertyAccessor(JsExpression object, String propertyName) { + // Might be not accurate enough. + return new JsNameRef(propertyName, object); + } + + private JsFunction generateJsMethod(@NotNull FunctionDescriptor functionDescriptor) { + JsName functionName = context.getNameForDescriptor(functionDescriptor); + JsScope enclosingScope = context.scope(); + JsFunction functionObject = JsAstUtils.createFunctionWithEmptyBody(enclosingScope); + JsPropertyInitializer initializer = new JsPropertyInitializer(functionName.makeRef(), functionObject); + output.add(initializer); + return functionObject; + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java index 9209f5d5b60..4dd47724e3f 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/intrinsic/functions/factories/TopLevelFIF.java @@ -65,6 +65,12 @@ public final class TopLevelFIF extends CompositeFIF { return new JsBinaryOperation(JsBinaryOperator.REF_EQ, receiver, arguments.get(0)); } }; + + @NotNull + public static final DescriptorPredicate HASH_CODE_IN_ANY = pattern("kotlin", "Any", "hashCode"); + @NotNull + public static final KotlinFunctionIntrinsic KOTLIN_HASH_CODE = new KotlinFunctionIntrinsic("hashCode"); + @NotNull private static final FunctionIntrinsic RETURN_RECEIVER_INTRINSIC = new FunctionIntrinsic() { @NotNull @@ -156,6 +162,7 @@ public final class TopLevelFIF extends CompositeFIF { add(pattern("kotlin", "toString").receiverExists(), TO_STRING); add(pattern("kotlin", "equals").receiverExists(), KOTLIN_EQUALS); add(pattern("kotlin", "identityEquals").receiverExists(), IDENTITY_EQUALS); + add(HASH_CODE_IN_ANY, KOTLIN_HASH_CODE); add(pattern(NamePredicate.PRIMITIVE_NUMBERS, "equals"), KOTLIN_EQUALS); add(pattern("String|Boolean|Char|Number.equals"), KOTLIN_EQUALS); add(pattern("kotlin", "arrayOfNulls"), new KotlinFunctionIntrinsic("nullArray")); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java index 345d2355eea..90097dbe12b 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java @@ -139,6 +139,11 @@ public final class JsAstUtils { return new JsBinaryOperation(JsBinaryOperator.SUB, left, right); } + @NotNull + public static JsBinaryOperation mul(@NotNull JsExpression left, @NotNull JsExpression right) { + return new JsBinaryOperation(JsBinaryOperator.MUL, left, right); + } + @NotNull public static JsPrefixOperation not(@NotNull JsExpression expression) { return new JsPrefixOperation(JsUnaryOperator.NOT, expression); diff --git a/js/js.translator/testData/dataClass/cases/components.kt b/js/js.translator/testData/dataClass/cases/components.kt new file mode 100644 index 00000000000..d132adb20a6 --- /dev/null +++ b/js/js.translator/testData/dataClass/cases/components.kt @@ -0,0 +1,24 @@ +package foo + +data class Dat(val start: String, middle: String, val end: String) { + fun getLabel() : String { + return start + end + } +} + +fun assertEquals(expected: T, actual: T) { + if (expected != actual) throw Exception("expected: $expected, actual, $actual") +} + +fun assertNotEqual(expected: T, actual: T) { + if (expected == actual) throw Exception("unexpectedly equal: $expected and $actual") +} + +fun box(): String { + val d = Dat("max", "-", "min") + assertEquals("maxmin", d.getLabel()) + val (p1, p2) = d + assertEquals("max", p1) + assertEquals("min", p2) + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dataClass/cases/copy.kt b/js/js.translator/testData/dataClass/cases/copy.kt new file mode 100644 index 00000000000..b39744143c2 --- /dev/null +++ b/js/js.translator/testData/dataClass/cases/copy.kt @@ -0,0 +1,11 @@ +package foo + +data class Dat(val start: String, middle: String, val end: String) + +fun box(): String { + val d1 = Dat("OO", "-", "PS") + val d2: Dat = d1.copy(end = "K", middle = "+") + val d3: Dat = d2.copy(start = "O", middle = "-") + val (p1, p2) = d3 + return p1 + p2 +} diff --git a/js/js.translator/testData/dataClass/cases/equals.kt b/js/js.translator/testData/dataClass/cases/equals.kt new file mode 100644 index 00000000000..4f3735b001c --- /dev/null +++ b/js/js.translator/testData/dataClass/cases/equals.kt @@ -0,0 +1,49 @@ +package foo + +data class Holder(val v: T) + +data class Dat(val start: String, val end: String) + +data class Dat2(val start: String, val end: String) + +class Obj(val start: String, val end: String) + +fun assertEquals(expected: T, actual: T) { + if (expected != actual) throw Exception("expected: $expected, actual, $actual") +} + +fun assertNotEqual(expected: T, actual: T) { + if (expected == actual) throw Exception("unexpectedly equal: $expected and $actual") +} + +fun box(): String { + val d1 = Dat("a", "b") + val d2 = Dat("a", "b") + val d3 = Dat("a", "c") + + val otherD1 = Dat2("a", "b") + + assertEquals(d1, d1) + assertEquals(d1, d2) + assertNotEqual(d1, d3) + + assertNotEqual(d1, otherD1) + + var hd1 = Holder(Dat("y", "n")) + var hd2 = Holder(Dat("y", "n")) + var hd3 = Holder(Dat("1", "2")) + + assertEquals(hd1, hd1) + assertEquals(hd1, hd2) + assertNotEqual(hd1, hd3) + + var ho1 = Holder(Obj("+", "-")) + var ho2 = Holder(Obj("+", "-")) + var ho3 = Holder(Obj("*", "*")) + + assertEquals(ho1, ho1) + assertNotEqual(ho1, ho2) + assertNotEqual(ho1, ho3) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dataClass/cases/hashcode.kt b/js/js.translator/testData/dataClass/cases/hashcode.kt new file mode 100644 index 00000000000..1bf3656a980 --- /dev/null +++ b/js/js.translator/testData/dataClass/cases/hashcode.kt @@ -0,0 +1,88 @@ +package foo + +import java.util.ArrayList + + +data class Holder(val v: T) + +data class Dat(val start: String, val end: String) + +class Obj(val start: String, val end: String) + + +fun assertEquals(expected: T, actual: T) { + if (expected != actual) throw Exception("expected: $expected, actual, $actual") +} + +fun assertSomeNotEqual(c: Iterable) { + val it = c.iterator() + val first = it.next() + while (it.hasNext()) { + val item: T = it.next() + if (item != first) { + return; + } + } + throw Exception("All elements are the same: $first") +} + +fun assertAllEqual(c: Iterable) { + val it = c.iterator() + val first = it.next() + while (it.hasNext()) { + val item: T = it.next() + assertEquals(first, item) + } +} + +val hashCoder: (o: Any) -> Int = { o -> o.hashCode() } + +val wrapInH = { (t: T) -> Holder(t) } + +fun box(): String { + + // Check that same Dat's have the same hashcode. + val sameDs = listOf(Dat("a", "b"), Dat("a", "b")) + assertAllEqual(map(sameDs, hashCoder)) + + // Check that different Dat's have different hashcodes (at least some of them). + val differentDs = listOf(Dat("a", "b"), Dat("a", "c"), Dat("a", "d")) + assertSomeNotEqual(map(differentDs, hashCoder)) + + // Check the same on Obj's, which should be always different and with different hashcodes. + val sameOs = listOf(Obj("a", "b"), Obj("a", "b"), Obj("a", "b")) + val differentOs = listOf(Obj("a", "b"), Obj("a", "b"), Obj("a", "b")) + + // Obj's are always different. + assertSomeNotEqual(map(sameOs, hashCoder)) + assertSomeNotEqual(map(differentOs, hashCoder)) + + // Both Dat's and Obj's wrapped as Holder should retain their hashcode relations. + val sameHDs = map(sameDs, wrapInH) + assertAllEqual(map(sameHDs, hashCoder)) + val differentHDs = map(differentDs, wrapInH) + assertSomeNotEqual(map(differentHDs, hashCoder)) + + val sameHOs = map(sameOs, wrapInH) + assertSomeNotEqual(map(sameHOs, hashCoder)) + val differentHOs = map(differentOs, wrapInH) + assertSomeNotEqual(map(differentHOs, hashCoder)) + + return "OK" +} + +fun listOf(vararg a: T): List { + val list = ArrayList() + for (e in a) { + list.add(e) + } + return list +} + +fun map(c : Iterable, transform : (T) -> S) : List { + val list = ArrayList() + for (t in c) { + list.add(transform(t)) + } + return list +} diff --git a/js/js.translator/testData/dataClass/cases/keyrole.kt b/js/js.translator/testData/dataClass/cases/keyrole.kt new file mode 100644 index 00000000000..b0a30fc4c53 --- /dev/null +++ b/js/js.translator/testData/dataClass/cases/keyrole.kt @@ -0,0 +1,27 @@ +package foo + +data class Holder(val v: T) + +data class Dat(val start: String, val end: String) + +class Obj(val start: String, val end: String) + +fun assertEquals(expected: T, actual: T) { + if (expected != actual) throw Exception("expected: $expected, actual, $actual") +} + +fun box(): String { + val setD = java.util.HashSet>() + setD.add(Holder(Dat("a", "b"))) + setD.add(Holder(Dat("a", "b"))) + setD.add(Holder(Dat("a", "b"))) + assertEquals(1, setD.size()) + + val setO = java.util.HashSet>() + setO.add(Holder(Obj("a", "b"))) + setO.add(Holder(Obj("a", "b"))) + setO.add(Holder(Obj("a", "b"))) + assertEquals(3, setO.size()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dataClass/cases/tostring.kt b/js/js.translator/testData/dataClass/cases/tostring.kt new file mode 100644 index 00000000000..2bd0a497c43 --- /dev/null +++ b/js/js.translator/testData/dataClass/cases/tostring.kt @@ -0,0 +1,27 @@ +package foo + +data class Holder(val v: T) + +data class Dat(val start: String, val end: String) + +class Obj(val start: String, val end: String) + +fun assertEquals(expected: T, actual: T) { + if (expected != actual) throw Exception("expected: $expected, actual, $actual") +} + +fun box(): String { + val d = Dat("a", "b") + + assertEquals("Dat(start=a, end=b)", "${d}") + + var hd = Holder(Dat("y", "n")) + + assertEquals("Holder(v=Dat(start=y, end=n))", "${hd}") + + var ho = Holder(Obj("+", "-")) + + assertEquals("Holder(v=[object Object])", "${ho}") + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/kotlin_lib.js b/js/js.translator/testData/kotlin_lib.js index 9ae569540d4..2b1965341df 100644 --- a/js/js.translator/testData/kotlin_lib.js +++ b/js/js.translator/testData/kotlin_lib.js @@ -48,6 +48,27 @@ return obj1 === obj2; }; + Kotlin.hashCode = function (obj) { + if (obj == null) { + return 0; + } + if ("function" == typeof obj.hashCode) { + return obj.hashCode(); + } + var objType = typeof obj; + if ("object" == objType || "function" == objType) { + return getObjectHashCode(obj); + } else if ("number" == objType) { + // TODO: a more elaborate code is needed for floating point values. + return obj | 0; + } if ("boolean" == objType) { + return Number(obj) + } + + var str = String(obj); + return getStringHashCode(str); + }; + Kotlin.toString = function (o) { if (o == null) { return "null"; @@ -97,6 +118,29 @@ }; } + /** @const */ + var POW_2_32 = 4294967296; + // TODO: consider switching to Symbol type once we are on ES6. + /** @const */ + var OBJECT_HASH_CODE_PROPERTY_NAME = "kotlinHashCodeValue$"; + + function getObjectHashCode(obj) { + if (!(OBJECT_HASH_CODE_PROPERTY_NAME in obj)) { + var hash = (Math.random() * POW_2_32) | 0; // Make 32-bit singed integer. + Object.defineProperty(obj, OBJECT_HASH_CODE_PROPERTY_NAME, { value: hash, enumerable: false }); + } + return obj[OBJECT_HASH_CODE_PROPERTY_NAME]; + } + + function getStringHashCode(str) { + var hash = 0; + for (var i = 0; i < str.length; i++) { + var code = str.charCodeAt(i); + hash = (hash * 31 + code) | 0; // Keep it 32-bit. + } + return hash; + } + /** * @interface * @template T diff --git a/js/js.translator/testData/maps.js b/js/js.translator/testData/maps.js index 75f781fc31b..50c95ffd123 100644 --- a/js/js.translator/testData/maps.js +++ b/js/js.translator/testData/maps.js @@ -64,12 +64,12 @@ } function equals_fixedValueHasEquals(fixedValue, variableValue) { - return fixedValue.equals(variableValue); + return fixedValue.equals_za3rmp$(variableValue); } function equals_fixedValueNoEquals(fixedValue, variableValue) { - return (typeof variableValue.equals == FUNCTION) ? - variableValue.equals(fixedValue) : (fixedValue === variableValue); + return (typeof variableValue.equals_za3rmp$ == FUNCTION) ? + variableValue.equals_za3rmp$(fixedValue) : (fixedValue === variableValue); } function createKeyValCheck(kvStr) { @@ -138,7 +138,7 @@ Bucket.prototype = { getEqualityFunction: function (searchValue) { - return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; + return (typeof searchValue.equals_za3rmp$ == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; }, getEntryForKey: createBucketSearcher(ENTRY), @@ -687,7 +687,7 @@ Kotlin.PrimitiveHashSet = Kotlin.createClassNow(Kotlin.AbstractCollection, return h; }; - this.equals = function (o) { + this.equals_za3rmp$ = function (o) { if (o === null || o === undefined) return false; if (this.size() === o.size()) { var iter1 = this.iterator();