Add intrinsics for compile-time computing trimMargin/trimIndent
These only apply when the receiver can be resolved to a constant and the margin prefix, if specified, is also a constant.
This commit is contained in:
committed by
Alexander Udalov
parent
2ec6ab92b5
commit
6ee987fa2e
@@ -64,10 +64,10 @@ public class IntrinsicMethods {
|
|||||||
private final IntrinsicsMap intrinsicsMap = new IntrinsicsMap();
|
private final IntrinsicsMap intrinsicsMap = new IntrinsicsMap();
|
||||||
|
|
||||||
public IntrinsicMethods(JvmTarget jvmTarget) {
|
public IntrinsicMethods(JvmTarget jvmTarget) {
|
||||||
this(jvmTarget, true);
|
this(jvmTarget, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntrinsicMethods(JvmTarget jvmTarget, boolean shouldThrowNpeOnExplicitEqualsForBoxedNull) {
|
public IntrinsicMethods(JvmTarget jvmTarget, boolean canReplaceStdlibRuntimeApiBehavior, boolean shouldThrowNpeOnExplicitEqualsForBoxedNull) {
|
||||||
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, RECEIVER_PARAMETER_FQ_NAME, "javaClass", -1, JavaClassProperty.INSTANCE);
|
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, RECEIVER_PARAMETER_FQ_NAME, "javaClass", -1, JavaClassProperty.INSTANCE);
|
||||||
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, KotlinBuiltIns.FQ_NAMES.kClass, "java", -1, new KClassJavaProperty());
|
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, KotlinBuiltIns.FQ_NAMES.kClass, "java", -1, new KClassJavaProperty());
|
||||||
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, KotlinBuiltIns.FQ_NAMES.kClass, "javaObjectType", -1, new KClassJavaObjectTypeProperty());
|
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, KotlinBuiltIns.FQ_NAMES.kClass, "javaObjectType", -1, new KClassJavaObjectTypeProperty());
|
||||||
@@ -143,6 +143,11 @@ public class IntrinsicMethods {
|
|||||||
declareIntrinsicFunction(FQ_NAMES.string, "plus", 1, new Concat());
|
declareIntrinsicFunction(FQ_NAMES.string, "plus", 1, new Concat());
|
||||||
declareIntrinsicFunction(FQ_NAMES.string, "get", 1, new StringGetChar());
|
declareIntrinsicFunction(FQ_NAMES.string, "get", 1, new StringGetChar());
|
||||||
|
|
||||||
|
if (canReplaceStdlibRuntimeApiBehavior) {
|
||||||
|
intrinsicsMap.registerIntrinsic(TEXT_PACKAGE_FQ_NAME, FQ_NAMES.string, "trimMargin", 1, new TrimMargin());
|
||||||
|
intrinsicsMap.registerIntrinsic(TEXT_PACKAGE_FQ_NAME, FQ_NAMES.string, "trimIndent", 0, new TrimIndent());
|
||||||
|
}
|
||||||
|
|
||||||
declareIntrinsicFunction(FQ_NAMES.cloneable, "clone", 0, CLONE);
|
declareIntrinsicFunction(FQ_NAMES.cloneable, "clone", 0, CLONE);
|
||||||
|
|
||||||
intrinsicsMap.registerIntrinsic(BUILT_INS_PACKAGE_FQ_NAME, KotlinBuiltIns.FQ_NAMES.any, "toString", 0, TO_STRING);
|
intrinsicsMap.registerIntrinsic(BUILT_INS_PACKAGE_FQ_NAME, KotlinBuiltIns.FQ_NAMES.any, "toString", 0, TO_STRING);
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.intrinsics
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.codegen.Callable
|
||||||
|
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||||
|
import org.jetbrains.kotlin.codegen.StackValue
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getReceiverExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_STRING_TYPE
|
||||||
|
|
||||||
|
class TrimMargin : IntrinsicMethod() {
|
||||||
|
override fun toCallable(fd: FunctionDescriptor, isSuper: Boolean, resolvedCall: ResolvedCall<*>, codegen: ExpressionCodegen): Callable {
|
||||||
|
return tryApply(resolvedCall, codegen)
|
||||||
|
?: codegen.state.typeMapper.mapToCallableMethod(fd, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryApply(resolvedCall: ResolvedCall<*>, codegen: ExpressionCodegen): Callable? {
|
||||||
|
val literalText = resolvedCall.getReceiverExpression()
|
||||||
|
?.let { codegen.getCompileTimeConstant(it) as? StringValue }
|
||||||
|
?.value ?: return null
|
||||||
|
|
||||||
|
val text = when (val argument = resolvedCall.valueArguments.values.single()) {
|
||||||
|
is DefaultValueArgument -> literalText.trimMargin()
|
||||||
|
is ExpressionValueArgument -> {
|
||||||
|
val marginPrefix = argument.valueArgument?.getArgumentExpression()
|
||||||
|
?.let { codegen.getCompileTimeConstant(it) as? StringValue }
|
||||||
|
?.value ?: return null
|
||||||
|
literalText.trimMargin(marginPrefix)
|
||||||
|
}
|
||||||
|
else -> error("Unknown value argument type ${argument::class}: $argument")
|
||||||
|
}
|
||||||
|
return StringConstant(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TrimIndent : IntrinsicMethod() {
|
||||||
|
override fun toCallable(fd: FunctionDescriptor, isSuper: Boolean, resolvedCall: ResolvedCall<*>, codegen: ExpressionCodegen): Callable {
|
||||||
|
return tryApply(resolvedCall, codegen)
|
||||||
|
?: codegen.state.typeMapper.mapToCallableMethod(fd, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryApply(resolvedCall: ResolvedCall<*>, codegen: ExpressionCodegen): Callable? {
|
||||||
|
val literalText = resolvedCall.getReceiverExpression()
|
||||||
|
?.let { codegen.getCompileTimeConstant(it) as? StringValue }
|
||||||
|
?.value ?: return null
|
||||||
|
|
||||||
|
val text = literalText.trimIndent()
|
||||||
|
return StringConstant(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StringConstant(private val text: String) : IntrinsicCallable(JAVA_STRING_TYPE, emptyList(), null, null), IntrinsicWithSpecialReceiver {
|
||||||
|
override fun invokeMethodWithArguments(resolvedCall: ResolvedCall<*>, receiver: StackValue, codegen: ExpressionCodegen) =
|
||||||
|
StackValue.constant(text, JAVA_STRING_TYPE)
|
||||||
|
}
|
||||||
@@ -200,7 +200,8 @@ class GenerationState private constructor(
|
|||||||
val intrinsics: IntrinsicMethods = run {
|
val intrinsics: IntrinsicMethods = run {
|
||||||
val shouldUseConsistentEquals = languageVersionSettings.supportsFeature(LanguageFeature.ThrowNpeOnExplicitEqualsForBoxedNull) &&
|
val shouldUseConsistentEquals = languageVersionSettings.supportsFeature(LanguageFeature.ThrowNpeOnExplicitEqualsForBoxedNull) &&
|
||||||
!configuration.getBoolean(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL)
|
!configuration.getBoolean(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL)
|
||||||
IntrinsicMethods(target, shouldUseConsistentEquals)
|
val canReplaceStdlibRuntimeApiBehavior = languageVersionSettings.apiVersion <= ApiVersion.parse(KotlinVersion.CURRENT.toString())!!
|
||||||
|
IntrinsicMethods(target, canReplaceStdlibRuntimeApiBehavior, shouldUseConsistentEquals)
|
||||||
}
|
}
|
||||||
val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this)
|
val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this)
|
||||||
val globalInlineContext: GlobalInlineContext = GlobalInlineContext(diagnostics)
|
val globalInlineContext: GlobalInlineContext = GlobalInlineContext(diagnostics)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
fun notConstant(arg: String): String {
|
||||||
|
return arg.trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interpolated(arg: Int):String {
|
||||||
|
return """
|
||||||
|
Hello,
|
||||||
|
$arg
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notInvoked():String {
|
||||||
|
return """
|
||||||
|
Hello,
|
||||||
|
World
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 LDC "\\n Hello,\\n World\\n "
|
||||||
|
// 2 INVOKESTATIC kotlin/text/StringsKt.trimIndent
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// IGNORE_BACKEND: JVM_IR
|
||||||
|
|
||||||
|
fun constant(): String {
|
||||||
|
return """
|
||||||
|
Hello,
|
||||||
|
World
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val HAS_INDENT = """Hello,
|
||||||
|
World"""
|
||||||
|
fun interpolatedUsingConstant(): String {
|
||||||
|
return """
|
||||||
|
Hello,
|
||||||
|
$HAS_INDENT
|
||||||
|
World
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 LDC "Hello,\\nWorld"
|
||||||
|
// 1 LDC "Hello,\\nHello,\\nWorld\\nWorld"
|
||||||
|
// 0 INVOKESTATIC kotlin/text/StringsKt.trimIndent
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
fun notConstant(arg: String): String {
|
||||||
|
return arg.trimMargin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notConstantCustomPrefix(arg: String): String {
|
||||||
|
return arg.trimMargin("###")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interpolated(arg: Int):String {
|
||||||
|
return """
|
||||||
|
|Hello,
|
||||||
|
|$arg
|
||||||
|
""".trimMargin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notInvoked():String {
|
||||||
|
return """
|
||||||
|
|Hello,
|
||||||
|
|World
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun constantWithNonConstantCustomPrefix(arg: String): String {
|
||||||
|
return """
|
||||||
|
|Hello,
|
||||||
|
|World
|
||||||
|
""".trimMargin(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 LDC "\\n \|Hello,\\n \|"
|
||||||
|
// 1 LDC "\\n "
|
||||||
|
// 2 LDC "\\n \|Hello,\\n \|World\\n "
|
||||||
|
// 4 INVOKESTATIC kotlin/text/StringsKt.trimMargin
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// IGNORE_BACKEND: JVM_IR
|
||||||
|
|
||||||
|
fun constant(): String {
|
||||||
|
return """
|
||||||
|
|Hello,
|
||||||
|
|World
|
||||||
|
""".trimMargin()
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val HAS_MARGIN = """Hello,
|
||||||
|
|World"""
|
||||||
|
fun interpolatedUsingConstant(): String {
|
||||||
|
return """
|
||||||
|
|Hello,
|
||||||
|
|$HAS_MARGIN
|
||||||
|
|World
|
||||||
|
""".trimMargin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun constantCustomPrefix(): String {
|
||||||
|
return """
|
||||||
|
###Hello,
|
||||||
|
###World
|
||||||
|
""".trimMargin(marginPrefix = "###")
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val OCTOTHORPE = '#'
|
||||||
|
fun constantCustomPrefixInterpolatedUsingConstant(): String {
|
||||||
|
return """
|
||||||
|
#@#Hello,
|
||||||
|
#@#World
|
||||||
|
""".trimMargin(marginPrefix = "$OCTOTHORPE@$OCTOTHORPE")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 LDC "Hello,\\nWorld"
|
||||||
|
// 1 LDC "Hello,\\nHello,\\nWorld\\nWorld"
|
||||||
|
// 0 LDC "###"
|
||||||
|
// 0 INVOKESTATIC kotlin/text/StringsKt.trimMargin
|
||||||
@@ -2545,6 +2545,39 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/bytecodeText/intrinsicsTrim")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class IntrinsicsTrim extends AbstractBytecodeTextTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInIntrinsicsTrim() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/intrinsicsTrim"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimIndentNegative.kt")
|
||||||
|
public void testTrimIndentNegative() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentNegative.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimIndentPositive.kt")
|
||||||
|
public void testTrimIndentPositive() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentPositive.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimMarginNegative.kt")
|
||||||
|
public void testTrimMarginNegative() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginNegative.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimMarginPositive.kt")
|
||||||
|
public void testTrimMarginPositive() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginPositive.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/jackAndJill")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/jackAndJill")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
+33
@@ -2545,6 +2545,39 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/bytecodeText/intrinsicsTrim")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class IntrinsicsTrim extends AbstractIrBytecodeTextTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInIntrinsicsTrim() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/intrinsicsTrim"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimIndentNegative.kt")
|
||||||
|
public void testTrimIndentNegative() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentNegative.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimIndentPositive.kt")
|
||||||
|
public void testTrimIndentPositive() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentPositive.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimMarginNegative.kt")
|
||||||
|
public void testTrimMarginNegative() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginNegative.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("trimMarginPositive.kt")
|
||||||
|
public void testTrimMarginPositive() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginPositive.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/jackAndJill")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/jackAndJill")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user