KT-8299 Make proper access to private fields in generated methods of data classes

This commit is contained in:
Alexey Andreev
2016-05-17 11:58:52 +03:00
parent fda9797dc4
commit 3cd7dcdb26
3 changed files with 59 additions and 23 deletions
@@ -50,4 +50,8 @@ public class DataClassTest extends SingleFileTranslationTest {
public void testKeyrole() throws Exception {
checkFooBoxIsOk();
}
public void testPrivateFields() throws Exception {
checkFooBoxIsOk();
}
}
@@ -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<? super JsPropertyInitializer> 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<KtParameter> constructorParameters) {
JsFunction functionObj = generateJsMethod(function);
JsFunctionScope funScope = functionObj.getScope();
assert function.getValueParameters().size() == constructorParameters.size();
List<JsExpression> constructorArguments = new ArrayList<JsExpression>(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();
@@ -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"
}