Generate specialized 'toString' for inline classes when possible
#KT-25613
This commit is contained in:
@@ -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
|
||||
|
||||
+1
-1
@@ -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"
|
||||
}
|
||||
+17
@@ -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
|
||||
+23
@@ -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
|
||||
+1
-1
@@ -34,4 +34,4 @@ fun test() {
|
||||
// 0 INVOKESTATIC Result.box-impl
|
||||
// 0 INVOKESTATIC Result.unbox-impl
|
||||
// 0 Result\$Failure
|
||||
// 53 Result
|
||||
// 52 Result
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user