diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DataClassTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DataClassTest.java index f83317acb48..5e584d2f3f4 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DataClassTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DataClassTest.java @@ -50,4 +50,8 @@ public class DataClassTest extends SingleFileTranslationTest { public void testKeyrole() throws Exception { checkFooBoxIsOk(); } + + public void testPrivateFields() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/JsDataClassGenerator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/JsDataClassGenerator.java index 55c6466b940..a6667a9d70e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/JsDataClassGenerator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/JsDataClassGenerator.java @@ -25,11 +25,16 @@ import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.psi.KtClassOrObject; import org.jetbrains.kotlin.psi.KtParameter; +import org.jetbrains.kotlin.resolve.BindingContext; +import org.jetbrains.kotlin.resolve.BindingContextUtils; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; import java.util.ArrayList; import java.util.List; +import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.and; +import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.or; + class JsDataClassGenerator extends DataClassMethodGenerator { private final TranslationContext context; private final List output; @@ -47,27 +52,37 @@ class JsDataClassGenerator extends DataClassMethodGenerator { @Override public void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter) { + PropertyDescriptor propertyDescriptor = context.bindingContext().get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameter); + assert propertyDescriptor != null : "Property descriptor is expected to be non-null"; + JsFunction functionObject = generateJsMethod(function); - JsExpression returnExpression = propertyAccessor(JsLiteral.THIS, context.getNameForDescriptor(parameter).toString()); + JsExpression returnExpression = JsAstUtils.fqnWithoutSideEffects(context.getNameForDescriptor(propertyDescriptor), JsLiteral.THIS); functionObject.getBody().getStatements().add(new JsReturn(returnExpression)); } @Override public void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List constructorParameters) { JsFunction functionObj = generateJsMethod(function); - JsFunctionScope funScope = functionObj.getScope(); assert function.getValueParameters().size() == constructorParameters.size(); List constructorArguments = new ArrayList(constructorParameters.size()); for (JsName closureFieldName : closureFieldNames) { - constructorArguments.add(propertyAccessor(JsLiteral.THIS, closureFieldName.getIdent())); + constructorArguments.add(JsAstUtils.fqnWithoutSideEffects(closureFieldName, JsLiteral.THIS)); } for (int i = 0; i < constructorParameters.size(); i++) { KtParameter constructorParam = constructorParameters.get(i); - JsName paramName = funScope.declareName(constructorParam.getName()); + + ValueParameterDescriptor parameterDescriptor = (ValueParameterDescriptor) BindingContextUtils.getNotNull( + context.bindingContext(), BindingContext.VALUE_PARAMETER, constructorParam); + + PropertyDescriptor propertyDescriptor = BindingContextUtils.getNotNull( + context.bindingContext(), BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameterDescriptor); + + JsName fieldName = context.getNameForDescriptor(propertyDescriptor); + JsName paramName = context.getNameForDescriptor(parameterDescriptor); functionObj.getParameters().add(new JsParameter(paramName)); @@ -80,9 +95,7 @@ class JsDataClassGenerator extends DataClassMethodGenerator { } else { JsExpression defaultCondition = JsAstUtils.equality(new JsNameRef(paramName), Namer.getUndefinedExpression()); - argumentValue = new JsConditional(defaultCondition, - propertyAccessor(JsLiteral.THIS, constructorParam.getName()), - parameterValue); + argumentValue = new JsConditional(defaultCondition, new JsNameRef(fieldName, JsLiteral.THIS), parameterValue); } constructorArguments.add(argumentValue); } @@ -105,9 +118,10 @@ class JsDataClassGenerator extends DataClassMethodGenerator { 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(context.namer().kotlin("toString"), propertyAccessor(JsLiteral.THIS, name)); + String printName = classProperties.get(i).getName().asString(); + JsName name = context.getNameForDescriptor(classProperties.get(i)); + JsExpression literal = jsProgram.getStringLiteral((i == 0 ? (getClassDescriptor().getName() + "(") : ", ") + printName + "="); + JsExpression expr = new JsInvocation(context.namer().kotlin("toString"), new JsNameRef(name, JsLiteral.THIS)); JsExpression component = JsAstUtils.sum(literal, expr); if (result == null) { result = component; @@ -134,8 +148,8 @@ class JsDataClassGenerator extends DataClassMethodGenerator { for (PropertyDescriptor prop : classProperties) { // TODO: we should statically check that we can call hashCode method directly. - JsExpression component = new JsInvocation(context.namer().kotlin("hashCode"), - propertyAccessor(JsLiteral.THIS, prop.getName().toString())); + JsName name = context.getNameForDescriptor(prop); + JsExpression component = new JsInvocation(context.namer().kotlin("hashCode"), new JsNameRef(name, JsLiteral.THIS)); 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, @@ -164,29 +178,23 @@ class JsDataClassGenerator extends DataClassMethodGenerator { JsExpression fieldChain = null; for (PropertyDescriptor prop : classProperties) { - String name = prop.getName().toString(); + JsName name = context.getNameForDescriptor(prop); JsExpression next = new JsInvocation(context.namer().kotlin("equals"), - propertyAccessor(JsLiteral.THIS, name), - propertyAccessor(new JsNameRef(paramName), name)); + new JsNameRef(name, JsLiteral.THIS), + new JsNameRef(name, new JsNameRef(paramName))); if (fieldChain == null) { fieldChain = next; } else { - fieldChain = JsAstUtils.and(fieldChain, next); + fieldChain = and(fieldChain, next); } } assert fieldChain != null; - JsExpression returnExpression = - JsAstUtils.or(referenceEqual, JsAstUtils.and(isNotNull, JsAstUtils.and(otherIsObject, JsAstUtils.and(prototypeEqual, fieldChain)))); + JsExpression returnExpression = or(referenceEqual, and(isNotNull, and(otherIsObject, 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(); diff --git a/js/js.translator/testData/dataClass/cases/privateFields.kt b/js/js.translator/testData/dataClass/cases/privateFields.kt new file mode 100644 index 00000000000..48cb74027a6 --- /dev/null +++ b/js/js.translator/testData/dataClass/cases/privateFields.kt @@ -0,0 +1,24 @@ +package foo + +data class A(private val x: Int) { + val y: Int + get() = x +} + +fun box(): String { + val a = A(23) + + assertEquals("A(x=23)", a.toString()) + assertEquals(23, a.copy().y) + assertEquals(42, a.copy(42).y) + + assertEquals(A(23), A(23)) + assertNotEquals(A(42), A(23)) + + val map = mapOf(A(23) to "*", A(42) to "@") + assertEquals("*", map[A(23)]) + assertEquals("@", map[A(42)]) + assertEquals(null, map[A(93)]) + + return "OK" +} \ No newline at end of file