toString() method for data classes and fake implementation for hashCode/equals

This commit is contained in:
Alex Tkachman
2012-09-16 15:56:48 +03:00
parent e317764a88
commit 58af365eb2
13 changed files with 236 additions and 40 deletions
@@ -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;
}
}