toString() method for data classes and fake implementation for hashCode/equals
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.jet.codegen;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.asm4.MethodVisitor;
|
||||
import org.jetbrains.asm4.Type;
|
||||
import org.jetbrains.asm4.commons.InstructionAdapter;
|
||||
@@ -116,7 +117,7 @@ public abstract class ClassBodyCodegen extends GenerationStateAware {
|
||||
}
|
||||
}
|
||||
|
||||
protected List<JetParameter> getPrimaryConstructorParameters() {
|
||||
protected @NotNull List<JetParameter> getPrimaryConstructorParameters() {
|
||||
if (myClass instanceof JetClass) {
|
||||
return ((JetClass) myClass).getPrimaryConstructorParameters();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.asm4.MethodVisitor;
|
||||
import org.jetbrains.asm4.Type;
|
||||
import org.jetbrains.asm4.commons.InstructionAdapter;
|
||||
import org.jetbrains.asm4.commons.Method;
|
||||
import org.jetbrains.jet.codegen.binding.CalculatedClosure;
|
||||
import org.jetbrains.jet.codegen.signature.BothSignatureWriter;
|
||||
import org.jetbrains.jet.codegen.signature.JvmMethodParameterKind;
|
||||
@@ -47,7 +48,7 @@ import java.util.*;
|
||||
|
||||
import static org.jetbrains.asm4.Opcodes.*;
|
||||
import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isClassObject;
|
||||
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE;
|
||||
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.*;
|
||||
|
||||
/**
|
||||
* @author abreslav
|
||||
@@ -369,4 +370,16 @@ public class CodegenUtil {
|
||||
iv.invokespecial(classAsmType.getInternalName(), "<init>", "()V");
|
||||
iv.putstatic(classAsmType.getInternalName(), JvmAbi.INSTANCE_FIELD, classAsmType.getDescriptor());
|
||||
}
|
||||
|
||||
public static void generateStringBuilderConstructor(InstructionAdapter v) {
|
||||
v.visitTypeInsn(NEW, "java/lang/StringBuilder");
|
||||
v.dup();
|
||||
v.invokespecial("java/lang/StringBuilder", "<init>", "()V");
|
||||
}
|
||||
|
||||
public static void invokeAppendMethod(InstructionAdapter v, Type exprType) {
|
||||
Method appendDescriptor = new Method("append", getType(StringBuilder.class),
|
||||
new Type[] {exprType.getSort() == Type.OBJECT ? (exprType.equals(JAVA_STRING_TYPE) ? JAVA_STRING_TYPE : OBJECT_TYPE) : exprType});
|
||||
v.invokevirtual("java/lang/StringBuilder", "append", appendDescriptor.getDescriptor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -984,7 +984,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
return StackValue.constant(constantValue.toString(), type);
|
||||
}
|
||||
else {
|
||||
generateStringBuilderConstructor();
|
||||
generateStringBuilderConstructor(v);
|
||||
for (JetStringTemplateEntry entry : expression.getEntries()) {
|
||||
if (entry instanceof JetStringTemplateEntryWithExpression) {
|
||||
invokeAppend(entry.getExpression());
|
||||
@@ -994,7 +994,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
? ((JetEscapeStringTemplateEntry) entry).getUnescapedValue()
|
||||
: entry.getText();
|
||||
v.aconst(text);
|
||||
invokeAppendMethod(JAVA_STRING_TYPE);
|
||||
invokeAppendMethod(v, JAVA_STRING_TYPE);
|
||||
}
|
||||
}
|
||||
v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||
@@ -2568,14 +2568,6 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
}
|
||||
}
|
||||
|
||||
public void generateStringBuilderConstructor() {
|
||||
Type type = getType(StringBuilder.class);
|
||||
v.anew(type);
|
||||
v.dup();
|
||||
Method method = new Method("<init>", Type.VOID_TYPE, new Type[0]);
|
||||
v.invokespecial("java/lang/StringBuilder", method.getName(), method.getDescriptor());
|
||||
}
|
||||
|
||||
public void invokeAppend(final JetExpression expr) {
|
||||
if (expr instanceof JetBinaryExpression) {
|
||||
final JetBinaryExpression binaryExpression = (JetBinaryExpression) expr;
|
||||
@@ -2594,13 +2586,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
}
|
||||
Type exprType = expressionType(expr);
|
||||
gen(expr, exprType);
|
||||
invokeAppendMethod(exprType.getSort() == Type.ARRAY ? OBJECT_TYPE : exprType);
|
||||
}
|
||||
|
||||
public void invokeAppendMethod(Type exprType) {
|
||||
Method appendDescriptor = new Method("append", getType(StringBuilder.class),
|
||||
new Type[] {exprType.getSort() == Type.OBJECT ? OBJECT_TYPE : exprType});
|
||||
v.invokevirtual("java/lang/StringBuilder", "append", appendDescriptor.getDescriptor());
|
||||
invokeAppendMethod(v, exprType.getSort() == Type.ARRAY ? OBJECT_TYPE : exprType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -322,10 +322,14 @@ public class FunctionCodegen extends GenerationStateAware {
|
||||
public static void genJetAnnotations(
|
||||
@NotNull GenerationState state,
|
||||
@NotNull FunctionDescriptor functionDescriptor,
|
||||
@NotNull JvmMethodSignature jvmSignature,
|
||||
@Nullable JvmMethodSignature jvmSignature,
|
||||
@Nullable String propertyTypeSignature,
|
||||
MethodVisitor mv
|
||||
) {
|
||||
if (jvmSignature == null) {
|
||||
jvmSignature = state.getTypeMapper().mapToCallableMethod(functionDescriptor, false, OwnerKind.IMPLEMENTATION).getSignature();
|
||||
}
|
||||
|
||||
List<ValueParameterDescriptor> paramDescrs = functionDescriptor.getValueParameters();
|
||||
Modality modality = functionDescriptor.getModality();
|
||||
ReceiverDescriptor receiverParameter = functionDescriptor.getReceiverParameter();
|
||||
|
||||
@@ -59,6 +59,7 @@ import static org.jetbrains.asm4.Opcodes.*;
|
||||
import static org.jetbrains.jet.codegen.CodegenUtil.*;
|
||||
import static org.jetbrains.jet.codegen.binding.CodegenBinding.*;
|
||||
import static org.jetbrains.jet.lang.resolve.BindingContextUtils.callableDescriptorToDeclaration;
|
||||
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.JAVA_STRING_TYPE;
|
||||
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE;
|
||||
|
||||
/**
|
||||
@@ -352,11 +353,103 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
|
||||
generateEnumMethods();
|
||||
|
||||
generateComponentFunctionsForDataClasses();
|
||||
generateFunctionsForDataClasses();
|
||||
|
||||
generateClosureFields(context.closure, v, state.getTypeMapper());
|
||||
}
|
||||
|
||||
private void generateFunctionsForDataClasses() {
|
||||
if (!JetStandardLibrary.isData(descriptor)) return;
|
||||
|
||||
generateComponentFunctionsForDataClasses();
|
||||
|
||||
generateDataClassToStringMethod();
|
||||
generateDataClassHashCodeMethod();
|
||||
generateDataClassEqualsMethod();
|
||||
}
|
||||
|
||||
private void generateDataClassEqualsMethod() {
|
||||
// todo: this is fake implementation
|
||||
|
||||
final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, superClassAsmType.getInternalName(), "equals", "(Ljava/lang/Object;)Z");
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(-1,-1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void generateDataClassHashCodeMethod() {
|
||||
// todo: this is fake implementation
|
||||
|
||||
final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, superClassAsmType.getInternalName(), "hashCode", "()I");
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(-1,-1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void generateDataClassToStringMethod() {
|
||||
final MethodVisitor mv = v.getVisitor().visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
|
||||
final InstructionAdapter iv = new InstructionAdapter(mv);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
|
||||
generateStringBuilderConstructor(iv);
|
||||
|
||||
boolean first = true;
|
||||
for (JetParameter parameter : getPrimaryConstructorParameters()) {
|
||||
if (parameter.getValOrVarNode() == null) continue;
|
||||
|
||||
PropertyDescriptor propertyDescriptor = state.getBindingContext().get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter);
|
||||
assert propertyDescriptor != null;
|
||||
|
||||
if(first) {
|
||||
iv.aconst(descriptor.getName() + "{" + propertyDescriptor.getName().getName()+"=");
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
iv.aconst(", " + propertyDescriptor.getName().getName()+"=");
|
||||
}
|
||||
CodegenUtil.invokeAppendMethod(iv, JAVA_STRING_TYPE);
|
||||
|
||||
iv.load(0, classAsmType);
|
||||
|
||||
// todo: seems to be more correct - need to be changed in sync with 'componentX' generation and related tests
|
||||
// final Method
|
||||
// method = typeMapper.mapGetterSignature(propertyDescriptor, OwnerKind.IMPLEMENTATION).getJvmMethodSignature().getAsmMethod();
|
||||
//
|
||||
// iv.invokevirtual(classAsmType.getInternalName(), method.getName(), method.getDescriptor());
|
||||
// final Type type = method.getReturnType();
|
||||
Type type = typeMapper.mapType(propertyDescriptor);
|
||||
iv.getfield(classAsmType.getInternalName(), parameter.getName(), type.getDescriptor());
|
||||
|
||||
if (type.getSort() == Type.ARRAY) {
|
||||
final Type elementType = correctElementType(type);
|
||||
if (elementType.getSort() == Type.OBJECT || elementType.getSort() == Type.ARRAY) {
|
||||
iv.invokestatic("java/util/Arrays", "toString", "([Ljava/lang/Object;)Ljava/lang/String;");
|
||||
type = JAVA_STRING_TYPE;
|
||||
}
|
||||
else {
|
||||
if (elementType.getSort() != Type.CHAR) {
|
||||
iv.invokestatic("java/util/Arrays", "toString", "(" + type.getDescriptor() + ")Ljava/lang/String;");
|
||||
type = JAVA_STRING_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
CodegenUtil.invokeAppendMethod(iv, type);
|
||||
}
|
||||
|
||||
iv.aconst("}");
|
||||
CodegenUtil.invokeAppendMethod(iv, JAVA_STRING_TYPE);
|
||||
|
||||
iv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||
iv.areturn(JAVA_STRING_TYPE);
|
||||
|
||||
FunctionCodegen.endVisit(mv, "toString", myClass);
|
||||
}
|
||||
|
||||
private void generateComponentFunctionsForDataClasses() {
|
||||
if (!myClass.hasPrimaryConstructor() || !JetStandardLibrary.isData(descriptor)) return;
|
||||
|
||||
@@ -376,19 +469,12 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
Type componentType = typeMapper.mapReturnType(returnType);
|
||||
|
||||
MethodVisitor mv = v.newMethod(myClass,
|
||||
ACC_FINAL | getVisibilityAccessFlag(function),
|
||||
FunctionCodegen.getMethodAsmFlags(function, OwnerKind.IMPLEMENTATION),
|
||||
function.getName().getName(),
|
||||
"()" + componentType.getDescriptor(),
|
||||
null, null);
|
||||
|
||||
JetMethodAnnotationWriter aw = JetMethodAnnotationWriter.visitAnnotation(mv);
|
||||
BitSet kotlinFlags = getFlagsForVisibility(function.getVisibility());
|
||||
aw.writeFlags(kotlinFlags);
|
||||
aw.writeKind(DescriptorKindUtils.kindToInt(function.getKind()));
|
||||
aw.writeNullableReturnType(returnType.isNullable());
|
||||
JvmMethodSignature jvmMethodSignature = typeMapper.mapToCallableMethod(function, false, kind).getSignature();
|
||||
aw.writeReturnType(jvmMethodSignature.getKotlinReturnType());
|
||||
aw.visitEnd();
|
||||
FunctionCodegen.genJetAnnotations(state, function, null, null, mv);
|
||||
|
||||
mv.visitCode();
|
||||
InstructionAdapter iv = new InstructionAdapter(mv);
|
||||
@@ -754,7 +840,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
private void genSimpleSuperCall(InstructionAdapter iv) {
|
||||
iv.load(0, superClassAsmType);
|
||||
if (descriptor.getKind() == ClassKind.ENUM_CLASS || descriptor.getKind() == ClassKind.ENUM_ENTRY) {
|
||||
iv.load(1, AsmTypeConstants.JAVA_STRING_TYPE);
|
||||
iv.load(1, JAVA_STRING_TYPE);
|
||||
iv.load(2, Type.INT_TYPE);
|
||||
iv.invokespecial(superClassAsmType.getInternalName(), "<init>", "(Ljava/lang/String;I)V");
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.asm4.Type;
|
||||
import org.jetbrains.asm4.commons.InstructionAdapter;
|
||||
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
|
||||
import org.jetbrains.jet.codegen.CodegenUtil;
|
||||
import org.jetbrains.jet.codegen.ExpressionCodegen;
|
||||
import org.jetbrains.jet.codegen.StackValue;
|
||||
import org.jetbrains.jet.codegen.state.GenerationState;
|
||||
import org.jetbrains.jet.lang.psi.JetExpression;
|
||||
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -43,15 +44,15 @@ public class Concat implements IntrinsicMethod {
|
||||
@NotNull GenerationState state
|
||||
) {
|
||||
if (receiver == null || receiver == StackValue.none()) { // LHS + RHS
|
||||
codegen.generateStringBuilderConstructor();
|
||||
CodegenUtil.generateStringBuilderConstructor(v);
|
||||
codegen.invokeAppend(arguments.get(0)); // StringBuilder(LHS)
|
||||
codegen.invokeAppend(arguments.get(1));
|
||||
}
|
||||
else { // LHS.plus(RHS)
|
||||
receiver.put(AsmTypeConstants.OBJECT_TYPE, v);
|
||||
codegen.generateStringBuilderConstructor();
|
||||
CodegenUtil.generateStringBuilderConstructor(v);
|
||||
v.swap(); // StringBuilder LHS
|
||||
codegen.invokeAppendMethod(expectedType); // StringBuilder(LHS)
|
||||
CodegenUtil.invokeAppendMethod(v, expectedType); // StringBuilder(LHS)
|
||||
codegen.invokeAppend(arguments.get(0));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
data class A(val x: Array<Int>?, val y: IntArray?)
|
||||
|
||||
fun box(): String {
|
||||
var ts = A(Array<Int>(2, {it}), IntArray(3)).toString()
|
||||
if(ts != "A{x=[0, 1], y=[0, 0, 0]}") return ts
|
||||
|
||||
ts = A(null, IntArray(3)).toString()
|
||||
if(ts != "A{x=null, y=[0, 0, 0]}") return ts
|
||||
|
||||
ts = A(null, null).toString()
|
||||
if(ts != "A{x=null, y=null}") return ts
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
data class A(var string: String)
|
||||
|
||||
fun box(): String {
|
||||
val a = A("Fail")
|
||||
if(a.toString() != "A{string=Fail}") return "fail"
|
||||
|
||||
a.string = "OK"
|
||||
if("$a" != "A{string=OK}") return a.toString()
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
data class A<T>(val x: T)
|
||||
|
||||
fun box(): String {
|
||||
val a = A(42)
|
||||
if ("$a" != "A{x=42}") return "$a"
|
||||
|
||||
val b = A(239.toLong())
|
||||
if ("$b" != "A{x=239}") return "$b"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
data class A(var x: Int, y: Int, val z: Int?)
|
||||
|
||||
fun box(): String {
|
||||
val a = A(1, 2, null)
|
||||
if("$a" != "A{x=1, z=null}") return "$a"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
open data class A(open val x: String)
|
||||
|
||||
class B : A("OK") {
|
||||
override val x: String = "Fail"
|
||||
}
|
||||
|
||||
fun foo(a: A) = a
|
||||
|
||||
fun box(): String {
|
||||
return if ("${foo(B())}" == "A{x=OK}") "OK" else "fail"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
data class A(val x: Unit)
|
||||
|
||||
fun box(): String {
|
||||
val a = A(#())
|
||||
return if ("$a" == "A{x=()}") "OK" else "$a"
|
||||
}
|
||||
@@ -13,23 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.jet.codegen;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import java.io.File;
|
||||
import org.jetbrains.jet.JetTestUtils;
|
||||
import org.jetbrains.jet.test.InnerTestClasses;
|
||||
import org.jetbrains.jet.test.TestMetadata;
|
||||
|
||||
import org.jetbrains.jet.codegen.AbstractDataClassCodegenTest;
|
||||
import java.io.File;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.jet.codegen.AbstractDataClassCodegenTest}. DO NOT MODIFY MANUALLY */
|
||||
@TestMetadata("compiler/testData/codegen/dataClasses")
|
||||
@InnerTestClasses({DataClassCodegenTestGenerated.Tostring.class})
|
||||
public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest {
|
||||
public void testAllFilesPresentInDataClasses() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest", new File("compiler/testData/codegen/dataClasses"), "kt", false);
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest", new File("compiler/testData/codegen/dataClasses"), "kt", true);
|
||||
}
|
||||
|
||||
@TestMetadata("arrayParams.kt")
|
||||
@@ -82,4 +82,49 @@ public class DataClassCodegenTestGenerated extends AbstractDataClassCodegenTest
|
||||
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/unitComponent.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/dataClasses/tostring")
|
||||
public static class Tostring extends AbstractDataClassCodegenTest {
|
||||
public void testAllFilesPresentInTostring() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.codegen.AbstractDataClassCodegenTest",
|
||||
new File("compiler/testData/codegen/dataClasses/tostring"), "kt", true);
|
||||
}
|
||||
|
||||
@TestMetadata("arrayParams.kt")
|
||||
public void testArrayParams() throws Exception {
|
||||
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/arrayParams.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("changingVarParam.kt")
|
||||
public void testChangingVarParam() throws Exception {
|
||||
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/changingVarParam.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("genericParam.kt")
|
||||
public void testGenericParam() throws Exception {
|
||||
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/genericParam.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mixedParams.kt")
|
||||
public void testMixedParams() throws Exception {
|
||||
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/mixedParams.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overriddenProperty.kt")
|
||||
public void testOverriddenProperty() throws Exception {
|
||||
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/overriddenProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unitComponent.kt")
|
||||
public void testUnitComponent() throws Exception {
|
||||
blackBoxFileByFullPath("compiler/testData/codegen/dataClasses/tostring/unitComponent.kt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Test suite() {
|
||||
TestSuite suite = new TestSuite("DataClassCodegenTestGenerated");
|
||||
suite.addTestSuite(DataClassCodegenTestGenerated.class);
|
||||
suite.addTestSuite(Tostring.class);
|
||||
return suite;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user