Improve string concatentation & string templates code generation

Reuse StringBuilder instances for nested subexpressions.
(NB StringBuilder instance for string template with a string
concatenation inside an expression entry, such as `"${"a" + "b"}"`,
will not be reused, although that doesn't seem to be a real-life issue).

 #KT-18558 Fixed Target versions 1.1.4
 #KT-13682 Fixed Target versions 1.1.4

Join adjacent strings literals, escaped strings, and constant values
(in a language version that supports const val inlining).
Use StringBuilder#append(char) for single-character constants
(e.g., " " in "$a $b").

 #KT-17280 Fixed Target versions 1.1.4
 #KT-15235 Fixed Target versions 1.1.4
This commit is contained in:
Dmitry Petrov
2017-06-20 17:08:39 +03:00
parent ae71833a12
commit a84c2a6f31
17 changed files with 313 additions and 56 deletions
@@ -74,8 +74,7 @@ import org.jetbrains.kotlin.resolve.calls.model.*;
import org.jetbrains.kotlin.resolve.calls.util.CallMaker;
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject;
import org.jetbrains.kotlin.resolve.calls.util.UnderscoreUtilKt;
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.constants.*;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluatorKt;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
@@ -826,14 +825,71 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@Override
public StackValue visitStringTemplateExpression(@NotNull KtStringTemplateExpression expression, StackValue receiver) {
StringBuilder constantValue = new StringBuilder("");
KtStringTemplateEntry[] entries = expression.getEntries();
List<StringTemplateEntry> entries = preprocessStringTemplate(expression);
if (entries.length == 1 && entries[0] instanceof KtStringTemplateEntryWithExpression) {
KtExpression expr = entries[0].getExpression();
return genToString(gen(expr), expressionType(expr));
if (entries.size() == 1) {
StringTemplateEntry entry = entries.get(0);
if (entry instanceof StringTemplateEntry.Expression) {
KtExpression expr = ((StringTemplateEntry.Expression) entry).expression;
return genToString(gen(expr), expressionType(expr));
}
else {
Type type = expressionType(expression);
return StackValue.constant(((StringTemplateEntry.Constant) entry).value, type);
}
}
return StackValue.operation(JAVA_STRING_TYPE, v -> {
genStringBuilderConstructor(v);
invokeAppendForEntries(v, entries);
v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
return Unit.INSTANCE;
});
}
private void invokeAppendForEntries(InstructionAdapter v, List<StringTemplateEntry> entries) {
for (StringTemplateEntry entry : entries) {
if (entry instanceof StringTemplateEntry.Expression) {
invokeAppend(v, ((StringTemplateEntry.Expression) entry).expression);
}
else {
String value = ((StringTemplateEntry.Constant) entry).value;
if (value.length() == 1) {
v.iconst(value.charAt(0));
genInvokeAppendMethod(v, Type.CHAR_TYPE);
}
else {
v.aconst(value);
genInvokeAppendMethod(v, JAVA_STRING_TYPE);
}
}
}
}
private static abstract class StringTemplateEntry {
static class Constant extends StringTemplateEntry {
final String value;
Constant(String value) {
this.value = value;
}
}
static class Expression extends StringTemplateEntry {
final KtExpression expression;
Expression(KtExpression expression) {
this.expression = expression;
}
}
}
private @NotNull List<StringTemplateEntry> preprocessStringTemplate(@NotNull KtStringTemplateExpression expression) {
KtStringTemplateEntry[] entries = expression.getEntries();
List<StringTemplateEntry> result = new ArrayList<>(entries.length);
StringBuilder constantValue = new StringBuilder("");
for (KtStringTemplateEntry entry : entries) {
if (entry instanceof KtLiteralStringTemplateEntry) {
constantValue.append(entry.getText());
@@ -841,36 +897,46 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
else if (entry instanceof KtEscapeStringTemplateEntry) {
constantValue.append(((KtEscapeStringTemplateEntry) entry).getUnescapedValue());
}
else if (entry instanceof KtStringTemplateEntryWithExpression) {
KtExpression entryExpression = entry.getExpression();
if (entryExpression == null) throw new AssertionError("No expression in " + entry);
ConstantValue<?> compileTimeConstant =
getPrimitiveOrStringCompileTimeConstant(entryExpression, bindingContext, state.getShouldInlineConstVals());
if (compileTimeConstant != null && isConstantValueInlinableInStringTemplate(compileTimeConstant)) {
constantValue.append(String.valueOf(compileTimeConstant.getValue()));
}
else {
result.add(new StringTemplateEntry.Constant(constantValue.toString()));
constantValue.setLength(0);
result.add(new StringTemplateEntry.Expression(entryExpression));
}
}
else {
constantValue = null;
break;
throw new AssertionError("Unexpected string template entry: " + entry);
}
}
if (constantValue != null) {
Type type = expressionType(expression);
return StackValue.constant(constantValue.toString(), type);
}
else {
return StackValue.operation(JAVA_STRING_TYPE, v -> {
genStringBuilderConstructor(v);
for (KtStringTemplateEntry entry : entries) {
if (entry instanceof KtStringTemplateEntryWithExpression) {
invokeAppend(entry.getExpression());
}
else {
String text = entry instanceof KtEscapeStringTemplateEntry
? ((KtEscapeStringTemplateEntry) entry).getUnescapedValue()
: entry.getText();
v.aconst(text);
genInvokeAppendMethod(v, JAVA_STRING_TYPE);
}
}
v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
return Unit.INSTANCE;
});
String leftoverConstantValue = constantValue.toString();
if (leftoverConstantValue.length() > 0) {
result.add(new StringTemplateEntry.Constant(leftoverConstantValue));
}
return result;
}
private static boolean isConstantValueInlinableInStringTemplate(@NotNull ConstantValue<?> constant) {
return constant instanceof StringValue ||
constant instanceof BooleanValue ||
constant instanceof DoubleValue ||
constant instanceof FloatValue ||
constant instanceof IntegerValueConstant ||
constant instanceof NullValue;
}
@Override
public StackValue visitBlockExpression(@NotNull KtBlockExpression expression, StackValue receiver) {
return generateBlock(expression, false);
@@ -3250,22 +3316,31 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
}
}
public void invokeAppend(KtExpression expr) {
public void invokeAppend(InstructionAdapter v, KtExpression expr) {
expr = KtPsiUtil.safeDeparenthesize(expr);
ConstantValue<?> compileTimeConstant = getPrimitiveOrStringCompileTimeConstant(expr, bindingContext, state.getShouldInlineConstVals());
if (compileTimeConstant == null && expr instanceof KtBinaryExpression) {
KtBinaryExpression binaryExpression = (KtBinaryExpression) expr;
if (binaryExpression.getOperationToken() == KtTokens.PLUS) {
KtExpression left = binaryExpression.getLeft();
KtExpression right = binaryExpression.getRight();
Type leftType = expressionType(left);
if (compileTimeConstant == null) {
if (expr instanceof KtBinaryExpression) {
KtBinaryExpression binaryExpression = (KtBinaryExpression) expr;
if (binaryExpression.getOperationToken() == KtTokens.PLUS) {
KtExpression left = binaryExpression.getLeft();
KtExpression right = binaryExpression.getRight();
Type leftType = expressionType(left);
if (leftType.equals(JAVA_STRING_TYPE)) {
invokeAppend(left);
invokeAppend(right);
return;
if (leftType.equals(JAVA_STRING_TYPE)) {
invokeAppend(v, left);
invokeAppend(v, right);
return;
}
}
}
else if (expr instanceof KtStringTemplateExpression) {
List<StringTemplateEntry> entries = preprocessStringTemplate((KtStringTemplateExpression) expr);
invokeAppendForEntries(v, entries);
return;
}
}
Type exprType = expressionType(expr);
@@ -42,8 +42,8 @@ class Concat : IntrinsicMethod() {
if (element is KtBinaryExpression && element.operationReference.getReferencedNameElementType() == KtTokens.PLUS) {
// LHS + RHS
genStringBuilderConstructor(v)
codegen.invokeAppend(element.left)
codegen.invokeAppend(element.right)
codegen.invokeAppend(v, element.left)
codegen.invokeAppend(v, element.right)
}
else {
// Explicit plus call LHS?.plus(RHS) or LHS.plus(RHS)
@@ -51,7 +51,7 @@ class Concat : IntrinsicMethod() {
genStringBuilderConstructor(v)
v.swap()
genInvokeAppendMethod(v, returnType)
codegen.invokeAppend(arguments[0])
codegen.invokeAppend(v, arguments[0])
}
v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
@@ -48,8 +48,8 @@ class Concat : IntrinsicMethod() {
if (element is KtBinaryExpression && element.operationReference.getReferencedNameElementType() == KtTokens.PLUS) {
// LHS + RHS
genStringBuilderConstructor(v)
codegen.invokeAppend(element.left)
codegen.invokeAppend(element.right)
codegen.invokeAppend(v, element.left)
codegen.invokeAppend(v, element.right)
}
else {
// LHS?.plus(RHS)
@@ -57,7 +57,7 @@ class Concat : IntrinsicMethod() {
genStringBuilderConstructor(v)
v.swap()
genInvokeAppendMethod(v, returnType)
codegen.invokeAppend(arguments.get(0))
codegen.invokeAppend(v, arguments.get(0))
}
v.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
+4 -5
View File
@@ -1,5 +1,4 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
object A {
const val a: String = "$"
@@ -7,7 +6,7 @@ object A {
const val c = 10000
val bNonConst = "1234$a"
val bNullable: String = "1234$a"
val bNullable: String? = "1234$a"
}
object B {
@@ -16,7 +15,7 @@ object B {
const val c = 10000
val bNonConst = "1234$a"
val bNullable: String = "1234$a"
val bNullable: String? = "1234$a"
}
fun box(): String {
@@ -26,8 +25,8 @@ fun box(): String {
if (A.c !== B.c) return "Fail 3: A.c !== B.c"
if (A.bNonConst === B.bNonConst) return "Fail 5: A.bNonConst == B.bNonConst"
if (A.bNullable === B.bNullable) return "Fail 6: A.bNullable == B.bNullable"
if (A.bNonConst !== B.bNonConst) return "Fail 4: A.bNonConst !== B.bNonConst"
if (A.bNullable !== B.bNullable) return "Fail 5: A.bNullable !== B.bNullable"
return "OK"
}
+34
View File
@@ -0,0 +1,34 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
// LANGUAGE_VERSION: 1.0
object A {
const val a: String = "$"
const val b = "1234$a"
const val c = 10000
val bNonConst = "1234$a"
val bNullable: String? = "1234$a"
}
object B {
const val a: String = "$"
const val b = "1234$a"
const val c = 10000
val bNonConst = "1234$a"
val bNullable: String? = "1234$a"
}
fun box(): String {
if (A.a !== B.a) return "Fail 1: A.a !== B.a"
if (A.b !== B.b) return "Fail 2: A.b !== B.b"
if (A.c !== B.c) return "Fail 3: A.c !== B.c"
if (A.bNonConst === B.bNonConst) return "Fail 4: A.bNonConst === B.bNonConst"
if (A.bNullable === B.bNullable) return "Fail 5: A.bNullable === B.bNullable"
return "OK"
}
@@ -0,0 +1,25 @@
// WITH_RUNTIME
import kotlin.test.assertEquals
const val constTrue = true
const val const42 = 42
const val constPiF = 3.14F
const val constPi = 3.1415926358
const val constString = "string"
fun box(): String {
assertEquals("true", "$constTrue")
assertEquals("42", "$const42")
assertEquals("3.14", "$constPiF")
assertEquals("3.1415926358", "$constPi")
assertEquals("string", "$constString")
assertEquals(constPi.toString(), "$constPi")
assertEquals((constPi * constPi).toString(), "${constPi * constPi}")
assertEquals("null", "${null}")
assertEquals("42", "${42}")
return "OK"
}
+21
View File
@@ -0,0 +1,21 @@
// WITH_RUNTIME
import kotlin.test.assertEquals
fun test1(s1: String, s2: String, s3: String) =
(s1 + s2) + s3
fun test2(s1: String, s2: String, s3: String) =
s1 + (s2 + s3)
fun test3(s1: String, s2: String, s3: String) =
"s1: $s1; " +
"s2: $s2; " +
"s3: $s3"
fun box(): String {
assertEquals("123", test1("1", "2", "3"))
assertEquals("123", test2("1", "2", "3"))
assertEquals("s1: 1; s2: 2; s3: 3", test3("1", "2", "3"))
return "OK"
}
+1 -1
View File
@@ -6,5 +6,5 @@ class kt5016 {
}
// 0 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/Object;\)Ljava/lang/StringBuilder
// 3 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder
// 2 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder
// 1 INVOKEVIRTUAL java/lang/StringBuilder.toString
+1 -1
View File
@@ -6,6 +6,6 @@ class kt5016int {
}
// 0 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/Object;\)Ljava/lang/StringBuilder
// 2 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder
// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder
// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(I\)Ljava/lang/StringBuilder
// 1 INVOKEVIRTUAL java/lang/StringBuilder.toString
+1 -1
View File
@@ -6,5 +6,5 @@ class kt5016intOrNull {
}
// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/Object;\)Ljava/lang/StringBuilder
// 2 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder
// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder
// 1 INVOKEVIRTUAL java/lang/StringBuilder.toString
@@ -0,0 +1,4 @@
val c = 1
fun foo() = "a\"($c)\"d"
// 3 INVOKEVIRTUAL java/lang/StringBuilder.append
@@ -0,0 +1,15 @@
fun test1(s1: String, s2: String, s3: String) =
(s1 + s2) + s3
fun test2(s1: String, s2: String, s3: String) =
s1 + (s2 + s3)
fun test3(s1: String, s2: String, s3: String, s4: String) =
((s1 + s2) + ((s3 + s4)))
fun test4(s1: String, s2: String, s3: String) =
"s1: $s1; " +
"s2: $s2; " +
"s3: $s3"
// 4 NEW java/lang/StringBuilder
@@ -4157,6 +4157,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName);
}
@TestMetadata("kt9532_lv10.kt")
public void testKt9532_lv10() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/kt9532_lv10.kt");
doTest(fileName);
}
@TestMetadata("long.kt")
public void testLong() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/long.kt");
@@ -17510,6 +17516,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/strings"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("constInStringTemplate.kt")
public void testConstInStringTemplate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/constInStringTemplate.kt");
doTest(fileName);
}
@TestMetadata("ea35743.kt")
public void testEa35743() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/ea35743.kt");
@@ -17582,6 +17594,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName);
}
@TestMetadata("nestedConcat.kt")
public void testNestedConcat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/nestedConcat.kt");
doTest(fileName);
}
@TestMetadata("rawStrings.kt")
public void testRawStrings() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/rawStrings.kt");
@@ -4157,6 +4157,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("kt9532_lv10.kt")
public void testKt9532_lv10() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/kt9532_lv10.kt");
doTest(fileName);
}
@TestMetadata("long.kt")
public void testLong() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/long.kt");
@@ -17510,6 +17516,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/strings"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("constInStringTemplate.kt")
public void testConstInStringTemplate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/constInStringTemplate.kt");
doTest(fileName);
}
@TestMetadata("ea35743.kt")
public void testEa35743() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/ea35743.kt");
@@ -17582,6 +17594,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("nestedConcat.kt")
public void testNestedConcat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/nestedConcat.kt");
doTest(fileName);
}
@TestMetadata("rawStrings.kt")
public void testRawStrings() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/rawStrings.kt");
@@ -1988,6 +1988,18 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
doTest(fileName);
}
@TestMetadata("kt15235.kt")
public void testKt15235() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/stringOperations/kt15235.kt");
doTest(fileName);
}
@TestMetadata("nestedConcat.kt")
public void testNestedConcat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/stringOperations/nestedConcat.kt");
doTest(fileName);
}
@TestMetadata("nonNullableStringPlus.kt")
public void testNonNullableStringPlus() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/stringOperations/nonNullableStringPlus.kt");
@@ -4157,6 +4157,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName);
}
@TestMetadata("kt9532_lv10.kt")
public void testKt9532_lv10() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/kt9532_lv10.kt");
doTest(fileName);
}
@TestMetadata("long.kt")
public void testLong() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/long.kt");
@@ -17510,6 +17516,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/strings"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("constInStringTemplate.kt")
public void testConstInStringTemplate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/constInStringTemplate.kt");
doTest(fileName);
}
@TestMetadata("ea35743.kt")
public void testEa35743() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/ea35743.kt");
@@ -17582,6 +17594,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName);
}
@TestMetadata("nestedConcat.kt")
public void testNestedConcat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/nestedConcat.kt");
doTest(fileName);
}
@TestMetadata("rawStrings.kt")
public void testRawStrings() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/rawStrings.kt");
@@ -4796,6 +4796,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("kt9532.kt")
public void testKt9532() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/kt9532.kt");
doTest(fileName);
}
@TestMetadata("kt9532_lv10.kt")
public void testKt9532_lv10() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constants/kt9532_lv10.kt");
try {
doTest(fileName);
}
@@ -21512,6 +21518,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/strings"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("constInStringTemplate.kt")
public void testConstInStringTemplate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/constInStringTemplate.kt");
doTest(fileName);
}
@TestMetadata("ea35743.kt")
public void testEa35743() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/ea35743.kt");
@@ -21590,6 +21602,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
doTest(fileName);
}
@TestMetadata("nestedConcat.kt")
public void testNestedConcat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/nestedConcat.kt");
doTest(fileName);
}
@TestMetadata("rawStrings.kt")
public void testRawStrings() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/strings/rawStrings.kt");