diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java index 5a009b79e48..802a57a199f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java @@ -64,10 +64,10 @@ public class IntrinsicMethods { private final IntrinsicsMap intrinsicsMap = new IntrinsicsMap(); 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, KotlinBuiltIns.FQ_NAMES.kClass, "java", -1, new KClassJavaProperty()); 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, "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); intrinsicsMap.registerIntrinsic(BUILT_INS_PACKAGE_FQ_NAME, KotlinBuiltIns.FQ_NAMES.any, "toString", 0, TO_STRING); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Trim.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Trim.kt new file mode 100644 index 00000000000..a1df74e6e71 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Trim.kt @@ -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) +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 909210436cf..5eba2ff1576 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -200,7 +200,8 @@ class GenerationState private constructor( val intrinsics: IntrinsicMethods = run { val shouldUseConsistentEquals = languageVersionSettings.supportsFeature(LanguageFeature.ThrowNpeOnExplicitEqualsForBoxedNull) && !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 globalInlineContext: GlobalInlineContext = GlobalInlineContext(diagnostics) diff --git a/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentNegative.kt b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentNegative.kt new file mode 100644 index 00000000000..631117bfbee --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentNegative.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentPositive.kt b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentPositive.kt new file mode 100644 index 00000000000..a044edfb3ed --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimIndentPositive.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginNegative.kt b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginNegative.kt new file mode 100644 index 00000000000..4f9cfd78bb5 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginNegative.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginPositive.kt b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginPositive.kt new file mode 100644 index 00000000000..900e8a8c135 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/intrinsicsTrim/trimMarginPositive.kt @@ -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 diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 89bfd239fc9..ad5514dfa1b 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -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") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index 071d84e2943..6f015391fea 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -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") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)