KT-8299 Make proper access to private fields in generated methods of data classes
This commit is contained in:
@@ -50,4 +50,8 @@ public class DataClassTest extends SingleFileTranslationTest {
|
|||||||
public void testKeyrole() throws Exception {
|
public void testKeyrole() throws Exception {
|
||||||
checkFooBoxIsOk();
|
checkFooBoxIsOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPrivateFields() throws Exception {
|
||||||
|
checkFooBoxIsOk();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-23
@@ -25,11 +25,16 @@ import org.jetbrains.kotlin.js.translate.context.TranslationContext;
|
|||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject;
|
import org.jetbrains.kotlin.psi.KtClassOrObject;
|
||||||
import org.jetbrains.kotlin.psi.KtParameter;
|
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 org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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 {
|
class JsDataClassGenerator extends DataClassMethodGenerator {
|
||||||
private final TranslationContext context;
|
private final TranslationContext context;
|
||||||
private final List<? super JsPropertyInitializer> output;
|
private final List<? super JsPropertyInitializer> output;
|
||||||
@@ -47,27 +52,37 @@ class JsDataClassGenerator extends DataClassMethodGenerator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter) {
|
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);
|
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));
|
functionObject.getBody().getStatements().add(new JsReturn(returnExpression));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<KtParameter> constructorParameters) {
|
public void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<KtParameter> constructorParameters) {
|
||||||
JsFunction functionObj = generateJsMethod(function);
|
JsFunction functionObj = generateJsMethod(function);
|
||||||
JsFunctionScope funScope = functionObj.getScope();
|
|
||||||
|
|
||||||
assert function.getValueParameters().size() == constructorParameters.size();
|
assert function.getValueParameters().size() == constructorParameters.size();
|
||||||
|
|
||||||
List<JsExpression> constructorArguments = new ArrayList<JsExpression>(constructorParameters.size());
|
List<JsExpression> constructorArguments = new ArrayList<JsExpression>(constructorParameters.size());
|
||||||
|
|
||||||
for (JsName closureFieldName : closureFieldNames) {
|
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++) {
|
for (int i = 0; i < constructorParameters.size(); i++) {
|
||||||
KtParameter constructorParam = constructorParameters.get(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));
|
functionObj.getParameters().add(new JsParameter(paramName));
|
||||||
|
|
||||||
@@ -80,9 +95,7 @@ class JsDataClassGenerator extends DataClassMethodGenerator {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
JsExpression defaultCondition = JsAstUtils.equality(new JsNameRef(paramName), Namer.getUndefinedExpression());
|
JsExpression defaultCondition = JsAstUtils.equality(new JsNameRef(paramName), Namer.getUndefinedExpression());
|
||||||
argumentValue = new JsConditional(defaultCondition,
|
argumentValue = new JsConditional(defaultCondition, new JsNameRef(fieldName, JsLiteral.THIS), parameterValue);
|
||||||
propertyAccessor(JsLiteral.THIS, constructorParam.getName()),
|
|
||||||
parameterValue);
|
|
||||||
}
|
}
|
||||||
constructorArguments.add(argumentValue);
|
constructorArguments.add(argumentValue);
|
||||||
}
|
}
|
||||||
@@ -105,9 +118,10 @@ class JsDataClassGenerator extends DataClassMethodGenerator {
|
|||||||
JsProgram jsProgram = context.program();
|
JsProgram jsProgram = context.program();
|
||||||
JsExpression result = null;
|
JsExpression result = null;
|
||||||
for (int i = 0; i < classProperties.size(); i++) {
|
for (int i = 0; i < classProperties.size(); i++) {
|
||||||
String name = classProperties.get(i).getName().toString();
|
String printName = classProperties.get(i).getName().asString();
|
||||||
JsExpression literal = jsProgram.getStringLiteral((i == 0 ? (getClassDescriptor().getName() + "(") : ", ") + name + "=");
|
JsName name = context.getNameForDescriptor(classProperties.get(i));
|
||||||
JsExpression expr = new JsInvocation(context.namer().kotlin("toString"), propertyAccessor(JsLiteral.THIS, name));
|
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);
|
JsExpression component = JsAstUtils.sum(literal, expr);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = component;
|
result = component;
|
||||||
@@ -134,8 +148,8 @@ class JsDataClassGenerator extends DataClassMethodGenerator {
|
|||||||
|
|
||||||
for (PropertyDescriptor prop : classProperties) {
|
for (PropertyDescriptor prop : classProperties) {
|
||||||
// TODO: we should statically check that we can call hashCode method directly.
|
// TODO: we should statically check that we can call hashCode method directly.
|
||||||
JsExpression component = new JsInvocation(context.namer().kotlin("hashCode"),
|
JsName name = context.getNameForDescriptor(prop);
|
||||||
propertyAccessor(JsLiteral.THIS, prop.getName().toString()));
|
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 newHashValue = JsAstUtils.sum(JsAstUtils.mul(new JsNameRef(varName), jsProgram.getNumberLiteral(31)), component);
|
||||||
JsExpression assignment = JsAstUtils.assignment(new JsNameRef(varName),
|
JsExpression assignment = JsAstUtils.assignment(new JsNameRef(varName),
|
||||||
new JsBinaryOperation(JsBinaryOperator.BIT_OR, newHashValue,
|
new JsBinaryOperation(JsBinaryOperator.BIT_OR, newHashValue,
|
||||||
@@ -164,29 +178,23 @@ class JsDataClassGenerator extends DataClassMethodGenerator {
|
|||||||
|
|
||||||
JsExpression fieldChain = null;
|
JsExpression fieldChain = null;
|
||||||
for (PropertyDescriptor prop : classProperties) {
|
for (PropertyDescriptor prop : classProperties) {
|
||||||
String name = prop.getName().toString();
|
JsName name = context.getNameForDescriptor(prop);
|
||||||
JsExpression next = new JsInvocation(context.namer().kotlin("equals"),
|
JsExpression next = new JsInvocation(context.namer().kotlin("equals"),
|
||||||
propertyAccessor(JsLiteral.THIS, name),
|
new JsNameRef(name, JsLiteral.THIS),
|
||||||
propertyAccessor(new JsNameRef(paramName), name));
|
new JsNameRef(name, new JsNameRef(paramName)));
|
||||||
if (fieldChain == null) {
|
if (fieldChain == null) {
|
||||||
fieldChain = next;
|
fieldChain = next;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fieldChain = JsAstUtils.and(fieldChain, next);
|
fieldChain = and(fieldChain, next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert fieldChain != null;
|
assert fieldChain != null;
|
||||||
|
|
||||||
JsExpression returnExpression =
|
JsExpression returnExpression = or(referenceEqual, and(isNotNull, and(otherIsObject, and(prototypeEqual, fieldChain))));
|
||||||
JsAstUtils.or(referenceEqual, JsAstUtils.and(isNotNull, JsAstUtils.and(otherIsObject, JsAstUtils.and(prototypeEqual, fieldChain))));
|
|
||||||
functionObj.getBody().getStatements().add(new JsReturn(returnExpression));
|
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) {
|
private JsFunction generateJsMethod(@NotNull FunctionDescriptor functionDescriptor) {
|
||||||
JsName functionName = context.getNameForDescriptor(functionDescriptor);
|
JsName functionName = context.getNameForDescriptor(functionDescriptor);
|
||||||
JsScope enclosingScope = context.scope();
|
JsScope enclosingScope = context.scope();
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user