Generate specialized 'toString' for inline classes when possible

#KT-25613
This commit is contained in:
Dmitry Petrov
2018-10-03 15:53:52 +03:00
parent f68ce4b35b
commit 5304754e88
13 changed files with 181 additions and 6 deletions
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.config.JvmTarget;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.load.java.JavaVisibilities;
import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
@@ -665,8 +666,23 @@ public class AsmUtil {
}
public static void genInvokeAppendMethod(@NotNull InstructionAdapter v, @NotNull Type type, @Nullable KotlinType kotlinType) {
genInvokeAppendMethod(v, type, kotlinType, null);
}
public static void genInvokeAppendMethod(
@NotNull InstructionAdapter v,
@NotNull Type type,
@Nullable KotlinType kotlinType,
@Nullable KotlinTypeMapper typeMapper
) {
Type appendParameterType;
if (kotlinType != null && InlineClassesUtilsKt.isInlineClassType(kotlinType)) {
CallableMethod specializedToString = getSpecializedToStringCallableMethodOrNull(kotlinType, typeMapper);
if (specializedToString != null) {
specializedToString.genInvokeInstruction(v);
appendParameterType = AsmTypes.JAVA_STRING_TYPE;
}
else if (kotlinType != null && InlineClassesUtilsKt.isInlineClassType(kotlinType)) {
appendParameterType = OBJECT_TYPE;
SimpleType nullableAnyType = kotlinType.getConstructor().getBuiltIns().getNullableAnyType();
StackValue.coerce(type, kotlinType, appendParameterType, nullableAnyType, v);
@@ -681,9 +697,17 @@ public class AsmUtil {
public static StackValue genToString(
@NotNull StackValue receiver,
@NotNull Type receiverType,
@Nullable KotlinType receiverKotlinType
@Nullable KotlinType receiverKotlinType,
@Nullable KotlinTypeMapper typeMapper
) {
return StackValue.operation(JAVA_STRING_TYPE, v -> {
CallableMethod specializedToString = getSpecializedToStringCallableMethodOrNull(receiverKotlinType, typeMapper);
if (specializedToString != null) {
receiver.put(receiverType, receiverKotlinType, v);
specializedToString.genInvokeInstruction(v);
return null;
}
Type type;
KotlinType kotlinType;
if (receiverKotlinType != null && InlineClassesUtilsKt.isInlineClassType(receiverKotlinType)) {
@@ -701,6 +725,36 @@ public class AsmUtil {
});
}
@Nullable
private static CallableMethod getSpecializedToStringCallableMethodOrNull(
@Nullable KotlinType receiverKotlinType,
@Nullable KotlinTypeMapper typeMapper
) {
if (typeMapper == null) return null;
if (receiverKotlinType == null) return null;
if (!InlineClassesUtilsKt.isInlineClassType(receiverKotlinType)) return null;
if (receiverKotlinType.isMarkedNullable()) return null;
DeclarationDescriptor receiverTypeDescriptor = receiverKotlinType.getConstructor().getDeclarationDescriptor();
assert receiverTypeDescriptor instanceof ClassDescriptor && ((ClassDescriptor) receiverTypeDescriptor).isInline() :
"Inline class type expected: " + receiverKotlinType;
ClassDescriptor receiverClassDescriptor = (ClassDescriptor) receiverTypeDescriptor;
FunctionDescriptor toStringDescriptor = receiverClassDescriptor.getUnsubstitutedMemberScope()
.getContributedFunctions(Name.identifier("toString"), NoLookupLocation.FROM_BACKEND)
.stream()
.filter(
f -> f.getValueParameters().size() == 0
&& KotlinBuiltIns.isString(f.getReturnType())
&& f.getDispatchReceiverParameter() != null
&& f.getExtensionReceiverParameter() == null
)
.findFirst()
.orElseThrow(() -> new AssertionError("'toString' not found in member scope of " + receiverClassDescriptor));
return typeMapper.mapToCallableMethod(toStringDescriptor, false, OwnerKind.ERASED_INLINE_CLASS);
}
static void genHashCode(MethodVisitor mv, InstructionAdapter iv, Type type, JvmTarget jvmTarget) {
if (type.getSort() == Type.ARRAY) {
Type elementType = correctElementType(type);
@@ -833,7 +833,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
StringTemplateEntry entry = entries.get(0);
if (entry instanceof StringTemplateEntry.Expression) {
KtExpression expr = ((StringTemplateEntry.Expression) entry).expression;
return genToString(gen(expr), expressionType(expr), kotlinType(expr));
return genToString(gen(expr), expressionType(expr), kotlinType(expr), typeMapper);
}
else {
return StackValue.constant(((StringTemplateEntry.Constant) entry).value, type);
@@ -3879,7 +3879,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
gen(expr, exprType, exprKotlinType);
}
genInvokeAppendMethod(v, exprType, exprKotlinType);
genInvokeAppendMethod(v, exprType, exprKotlinType, typeMapper);
}
@Nullable
@@ -122,7 +122,7 @@ public class FunctionsFromAnyGeneratorImpl extends FunctionsFromAnyGenerator {
}
}
}
genInvokeAppendMethod(iv, asmType, type.getKotlinType());
genInvokeAppendMethod(iv, asmType, type.getKotlinType(), typeMapper);
}
iv.aconst(")");
@@ -0,0 +1,46 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME
import kotlin.test.*
inline class Z(val value: Int)
fun test1_1(z: Z) = "$z"
fun test1_2(z: Z) = "$z$z"
fun test1_many(z: Z) = "$z $z $z"
fun test1_concat1(z: Z) = "-" + z
fun test1_concat2(z: Z) = "$z" + z
fun test1_concat3(z: Z) = "-" + z + z
fun test2_1(z: Z?) = "$z"
fun test2_2(z: Z?) = "$z$z"
fun test2_many(z: Z?) = "$z $z $z"
fun test2_concat1(z: Z?) = "-" + z
fun test2_concat2(z: Z?) = "$z" + z
fun test2_concat3(z: Z?) = "-" + z + z
fun box(): String {
assertEquals("Z(value=42)", test1_1(Z(42)))
assertEquals("Z(value=42)Z(value=42)", test1_2(Z(42)))
assertEquals("Z(value=42) Z(value=42) Z(value=42)", test1_many(Z(42)))
assertEquals("-Z(value=42)", test1_concat1(Z(42)))
assertEquals("Z(value=42)Z(value=42)", test1_concat2(Z(42)))
assertEquals("-Z(value=42)Z(value=42)", test1_concat3(Z(42)))
assertEquals("null", test2_1(null))
assertEquals("nullnull", test2_2(null))
assertEquals("null null null", test2_many(null))
assertEquals("-null", test2_concat1(null))
assertEquals("nullnull", test2_concat2(null))
assertEquals("-nullnull", test2_concat3(null))
assertEquals("Z(value=42)", test2_1(Z(42)))
assertEquals("Z(value=42)Z(value=42)", test2_2(Z(42)))
assertEquals("Z(value=42) Z(value=42) Z(value=42)", test2_many(Z(42)))
assertEquals("-Z(value=42)", test2_concat1(Z(42)))
assertEquals("Z(value=42)Z(value=42)", test2_concat2(Z(42)))
assertEquals("-Z(value=42)Z(value=42)", test2_concat3(Z(42)))
return "OK"
}
@@ -0,0 +1,17 @@
// !LANGUAGE: +InlineClasses
// FILE: Z.kt
inline class Z(val value: Int)
// FILE: test.kt
data class Data(val z1: Z, val z2: Z)
inline class Inline(val z: Z)
// @Data.class:
// 0 Z.box
// 0 Z.unbox
// @Inline.class:
// 0 Z.box
// 0 Z.unbox
@@ -0,0 +1,23 @@
// !LANGUAGE: +InlineClasses
// FILE: Z.kt
inline class Z(val value: Int)
// FILE: test.kt
fun test1_1(z: Z) = "$z"
fun test1_2(z: Z) = "$z$z"
fun test1_many(z: Z) = "$z $z $z"
fun test1_concat1(z: Z) = "-" + z
fun test1_concat2(z: Z) = "$z" + z
fun test1_concat3(z: Z) = "-" + z + z
fun test2_1(z: Z?) = "$z"
fun test2_2(z: Z?) = "$z$z"
fun test2_many(z: Z?) = "$z $z $z"
fun test2_concat1(z: Z?) = "-" + z
fun test2_concat2(z: Z?) = "$z" + z
fun test2_concat3(z: Z?) = "-" + z + z
// @TestKt.class:
// 0 box
// 0 unbox
@@ -34,4 +34,4 @@ fun test() {
// 0 INVOKESTATIC Result.box-impl
// 0 INVOKESTATIC Result.unbox-impl
// 0 Result\$Failure
// 53 Result
// 52 Result
@@ -11704,6 +11704,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt");
}
@TestMetadata("inlineClassInStringTemplate.kt")
public void testInlineClassInStringTemplate() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt");
}
@TestMetadata("inlineClassPropertyReferenceGetAndSet.kt")
public void testInlineClassPropertyReferenceGetAndSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt");
@@ -2136,6 +2136,16 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassBoxingUnboxingInsideInlinedLambda.kt");
}
@TestMetadata("inlineClassInGeneratedToString.kt")
public void testInlineClassInGeneratedToString() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt");
}
@TestMetadata("inlineClassInStringTemplate.kt")
public void testInlineClassInStringTemplate() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInStringTemplate.kt");
}
@TestMetadata("inlineClassesUnboxingAfterAssertionOperator.kt")
public void testInlineClassesUnboxingAfterAssertionOperator() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassesUnboxingAfterAssertionOperator.kt");
@@ -11704,6 +11704,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt");
}
@TestMetadata("inlineClassInStringTemplate.kt")
public void testInlineClassInStringTemplate() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt");
}
@TestMetadata("inlineClassPropertyReferenceGetAndSet.kt")
public void testInlineClassPropertyReferenceGetAndSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt");
@@ -11709,6 +11709,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt");
}
@TestMetadata("inlineClassInStringTemplate.kt")
public void testInlineClassInStringTemplate() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt");
}
@TestMetadata("inlineClassPropertyReferenceGetAndSet.kt")
public void testInlineClassPropertyReferenceGetAndSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt");
@@ -10239,6 +10239,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvoke.kt");
}
@TestMetadata("inlineClassInStringTemplate.kt")
public void testInlineClassInStringTemplate() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt");
}
@TestMetadata("inlineClassPropertyReferenceGetAndSet.kt")
public void testInlineClassPropertyReferenceGetAndSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt");
@@ -11284,6 +11284,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvoke.kt");
}
@TestMetadata("inlineClassInStringTemplate.kt")
public void testInlineClassInStringTemplate() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt");
}
@TestMetadata("inlineClassPropertyReferenceGetAndSet.kt")
public void testInlineClassPropertyReferenceGetAndSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt");