From 50d29e0526e1b35109689d7456d920807be2e102 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Thu, 7 Nov 2013 18:59:16 +0400 Subject: [PATCH] Support string templates in ConstantExpressionEvaluator --- .../evaluate/ConstantExpressionEvaluator.java | 110 +++++++++++++++++- .../evaluate/constantExpressionEvaluator.kt | 64 ++++++++++ .../parameters/expressions/escapedString.kt | 7 ++ .../parameters/expressions/multilineString.kt | 7 ++ .../parameters/expressions/stringPlusInt.kt | 7 ++ .../parameters/expressions/stringTemplate.kt | 19 +++ .../parameters/expressions/strings.kt | 9 ++ .../AnnotationParameterTestGenerated.java | 40 +++++++ 8 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/evaluate/constantExpressionEvaluator.kt create mode 100644 compiler/testData/resolveAnnotations/parameters/expressions/escapedString.kt create mode 100644 compiler/testData/resolveAnnotations/parameters/expressions/multilineString.kt create mode 100644 compiler/testData/resolveAnnotations/parameters/expressions/stringPlusInt.kt create mode 100644 compiler/testData/resolveAnnotations/parameters/expressions/stringTemplate.kt create mode 100644 compiler/testData/resolveAnnotations/parameters/expressions/strings.kt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/evaluate/ConstantExpressionEvaluator.java b/compiler/frontend/src/org/jetbrains/jet/lang/evaluate/ConstantExpressionEvaluator.java index 988f25c9627..5cd10ffd049 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/evaluate/ConstantExpressionEvaluator.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/evaluate/ConstantExpressionEvaluator.java @@ -25,14 +25,16 @@ import org.jetbrains.jet.lang.resolve.*; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument; import org.jetbrains.jet.lang.resolve.constants.*; +import org.jetbrains.jet.lang.resolve.constants.StringValue; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.types.JetType; -import javax.rmi.CORBA.ClassDesc; import java.util.List; import static org.jetbrains.jet.lang.resolve.BindingContext.COMPILE_TIME_INITIALIZER; +import static org.jetbrains.jet.lang.resolve.BindingContext.RESOLVED_CALL; +@SuppressWarnings("StaticMethodReferencedViaSubclass") public class ConstantExpressionEvaluator extends JetVisitor, Void> { @NotNull private final BindingTrace trace; @NotNull private final JetType expectedType; @@ -56,7 +58,111 @@ public class ConstantExpressionEvaluator extends JetVisitor visitStringTemplateExpression(@NotNull JetStringTemplateExpression expression, Void nothing) { - return trace.get(BindingContext.COMPILE_TIME_VALUE, expression); + CompileTimeConstant compileTimeConstant = trace.get(BindingContext.COMPILE_TIME_VALUE, expression); + if (compileTimeConstant != null) { + return compileTimeConstant; + } + + JetStringTemplateEntry[] stringTemplateEntries = expression.getEntries(); + if (stringTemplateEntries.length == 1) { + CompileTimeConstant singleEntry = stringTemplateEntries[0].accept(this, nothing); + if (!(singleEntry instanceof StringValue) && singleEntry != null) { + return new StringValue(singleEntry.getValue().toString()); + } + return singleEntry; + } + else if (stringTemplateEntries.length > 1) { + CompileTimeConstant first = stringTemplateEntries[0].accept(this, nothing); + if (first == null) { + return null; + } + CompileTimeConstant second; + String tmpResult = null; + for (int i = 1; i < stringTemplateEntries.length; i++) { + second = stringTemplateEntries[i].accept(this, nothing); + if (second == null) { + return null; + } + Object object = EvaluatePackage.evaluateBinaryExpression(first, second, Name.identifier("plus")); + if (object instanceof String) { + tmpResult = (String) object; + } + else { + tmpResult = object.toString(); + } + first = new StringValue(tmpResult); + } + + return new StringValue(tmpResult); + } + return null; + } + + @Override + public CompileTimeConstant visitBlockStringTemplateEntry(@NotNull JetBlockStringTemplateEntry entry, Void data) { + JetExpression expression = entry.getExpression(); + if (expression != null) { + return expression.accept(this, data); + } + return super.visitBlockStringTemplateEntry(entry, data); + } + + @Override + public CompileTimeConstant visitLiteralStringTemplateEntry(@NotNull JetLiteralStringTemplateEntry entry, Void data) { + return new StringValue(entry.getText()); + } + + @Override + public CompileTimeConstant visitSimpleNameStringTemplateEntry(@NotNull JetSimpleNameStringTemplateEntry entry, Void data) { + JetExpression expression = entry.getExpression(); + if (expression != null) { + return expression.accept(this, data); + } + return super.visitSimpleNameStringTemplateEntry(entry, data); + } + + @Override + public CompileTimeConstant visitEscapeStringTemplateEntry(@NotNull JetEscapeStringTemplateEntry entry, Void data) { + return new StringValue(entry.getUnescapedValue()); + } + + @Override + public CompileTimeConstant visitBinaryExpression(@NotNull JetBinaryExpression expression, Void data) { + JetExpression leftExpression = expression.getLeft(); + if (leftExpression == null) { + return null; + } + JetExpression rightExpression = expression.getRight(); + if (rightExpression == null) { + return null; + } + + CompileTimeConstant leftConstant = leftExpression.accept(this, data); + if (leftConstant == null) { + return null; + } + + CompileTimeConstant rightConstant = rightExpression.accept(this, data); + if (rightConstant == null) { + return null; + } + + ResolvedCall resolvedCall = trace.getBindingContext().get(RESOLVED_CALL, expression.getOperationReference()); + if (resolvedCall != null) { + CallableDescriptor resultingDescriptor = resolvedCall.getResultingDescriptor(); + Object value = EvaluatePackage.evaluateBinaryExpression(leftConstant, rightConstant, resultingDescriptor.getName()); + if (value != null) { + return createCompileTimeConstant(value); + } + } + return null; + } + + private static CompileTimeConstant createCompileTimeConstant(@NotNull Object value) { + if (value instanceof String) { + return new StringValue((String) value); + } + return null; } @Override diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/evaluate/constantExpressionEvaluator.kt b/compiler/frontend/src/org/jetbrains/jet/lang/evaluate/constantExpressionEvaluator.kt new file mode 100644 index 00000000000..9a7a76902ba --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/evaluate/constantExpressionEvaluator.kt @@ -0,0 +1,64 @@ +package org.jetbrains.jet.lang.evaluate + +import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant +import org.jetbrains.jet.lang.resolve.name.Name + +fun evaluateBinaryExpression(firstCompileTimeConstant: CompileTimeConstant<*>, secondCompileTimeConstant: CompileTimeConstant<*>, functionName: Name): Any? { + val compileTimeTypeFirst = getCompileTimeType(firstCompileTimeConstant) + val compileTimeTypeSecond = getCompileTimeType(secondCompileTimeConstant) + if (compileTimeTypeFirst == null || compileTimeTypeSecond == null) { + return null + } + + val first = firstCompileTimeConstant.getValue() + val second = secondCompileTimeConstant.getValue() + + val function = binaryOperations[BinaryOperation(compileTimeTypeFirst, compileTimeTypeSecond, functionName)] + if (function != null) { + return function(first, second) + } + + return null +} + +fun getCompileTimeType(c: CompileTimeConstant<*>): CompileTimeType? = when (c.getValue()) { + is Int -> INT + is Byte -> BYTE + is Short -> SHORT + is Long -> LONG + is Double -> DOUBLE + is Float -> FLOAT + is Char -> CHAR + is Boolean -> BOOLEAN + is String -> STRING + else -> null +} + +private class CompileTimeType + +private val BYTE = CompileTimeType() +private val SHORT = CompileTimeType() +private val INT = CompileTimeType() +private val LONG = CompileTimeType() +private val DOUBLE = CompileTimeType() +private val FLOAT = CompileTimeType() +private val CHAR = CompileTimeType() +private val BOOLEAN = CompileTimeType() +private val STRING = CompileTimeType() + +private fun bOp(a: CompileTimeType, b: CompileTimeType, functionNameAsString: String, f: (A, B) -> Any) = BinaryOperation(a, b, Name.identifier(functionNameAsString)) to f as Function2 + +private data class BinaryOperation(val f: CompileTimeType, val s: CompileTimeType, val functionName: Name) + +private val binaryOperations = hashMapOf, (Any?, Any?) -> Any>( + // String + bOp(STRING, STRING, "plus", { a, b -> a + b }), + bOp(STRING, BYTE, "plus", { a, b -> a + b }), + bOp(STRING, SHORT, "plus", { a, b -> a + b }), + bOp(STRING, INT, "plus", { a, b -> a + b }), + bOp(STRING, LONG, "plus", { a, b -> a + b }), + bOp(STRING, DOUBLE, "plus", { a, b -> a + b }), + bOp(STRING, FLOAT, "plus", { a, b -> a + b }), + bOp(STRING, CHAR, "plus", { a, b -> a + b }) +) + diff --git a/compiler/testData/resolveAnnotations/parameters/expressions/escapedString.kt b/compiler/testData/resolveAnnotations/parameters/expressions/escapedString.kt new file mode 100644 index 00000000000..e4f157754a2 --- /dev/null +++ b/compiler/testData/resolveAnnotations/parameters/expressions/escapedString.kt @@ -0,0 +1,7 @@ +package test + +annotation class Ann(val s1: String) + +Ann(s1 = "\$ab") class MyClass + +// EXPECTED: Ann[s1 = "$ab": jet.String] diff --git a/compiler/testData/resolveAnnotations/parameters/expressions/multilineString.kt b/compiler/testData/resolveAnnotations/parameters/expressions/multilineString.kt new file mode 100644 index 00000000000..55cfad19dea --- /dev/null +++ b/compiler/testData/resolveAnnotations/parameters/expressions/multilineString.kt @@ -0,0 +1,7 @@ +package test + +annotation class Ann(val s1: String) + +Ann(s1 = """a""" + "b") class MyClass + +// EXPECTED: Ann[s1 = "ab": jet.String] diff --git a/compiler/testData/resolveAnnotations/parameters/expressions/stringPlusInt.kt b/compiler/testData/resolveAnnotations/parameters/expressions/stringPlusInt.kt new file mode 100644 index 00000000000..6fb452b858f --- /dev/null +++ b/compiler/testData/resolveAnnotations/parameters/expressions/stringPlusInt.kt @@ -0,0 +1,7 @@ +package test + +annotation class Ann(val s1: String) + +Ann(s1 = "a" + 1) class MyClass + +// EXPECTED: Ann[s1 = "a1": jet.String] diff --git a/compiler/testData/resolveAnnotations/parameters/expressions/stringTemplate.kt b/compiler/testData/resolveAnnotations/parameters/expressions/stringTemplate.kt new file mode 100644 index 00000000000..fb676e03c7e --- /dev/null +++ b/compiler/testData/resolveAnnotations/parameters/expressions/stringTemplate.kt @@ -0,0 +1,19 @@ +package test + +annotation class Ann( + val s1: String, + val s2: String, + val s3: String, + val s4: String +) + +val i = 1 + +Ann( + s1 = "a$i", + s2 = "a$i b", + s3 = "$i", + s4 = "a${i}a$i" +) class MyClass + +// EXPECTED: Ann[s1 = "a1": jet.String, s2 = "a1 b": jet.String, s3 = "1": jet.String, s4 = "a1a1": jet.String] diff --git a/compiler/testData/resolveAnnotations/parameters/expressions/strings.kt b/compiler/testData/resolveAnnotations/parameters/expressions/strings.kt new file mode 100644 index 00000000000..95e069742e8 --- /dev/null +++ b/compiler/testData/resolveAnnotations/parameters/expressions/strings.kt @@ -0,0 +1,9 @@ +package test + +annotation class Ann(val s1: String, val s2: String) + +val i = 1 + +Ann(s1 = "a" + "b", s2 = "a" + "a$i") class MyClass + +// EXPECTED: Ann[s1 = "ab": jet.String, s2 = "aa1": jet.String] diff --git a/compiler/tests/org/jetbrains/jet/resolve/annotation/AnnotationParameterTestGenerated.java b/compiler/tests/org/jetbrains/jet/resolve/annotation/AnnotationParameterTestGenerated.java index 41b618502aa..2ce05224b48 100644 --- a/compiler/tests/org/jetbrains/jet/resolve/annotation/AnnotationParameterTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/resolve/annotation/AnnotationParameterTestGenerated.java @@ -31,6 +31,7 @@ import org.jetbrains.jet.resolve.annotation.AbstractAnnotationParameterTest; /** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @TestMetadata("compiler/testData/resolveAnnotations/parameters") +@InnerTestClasses({AnnotationParameterTestGenerated.Expressions.class}) public class AnnotationParameterTestGenerated extends AbstractAnnotationParameterTest { public void testAllFilesPresentInParameters() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/resolveAnnotations/parameters"), Pattern.compile("^(.+)\\.kt$"), true); @@ -71,4 +72,43 @@ public class AnnotationParameterTestGenerated extends AbstractAnnotationParamete doTest("compiler/testData/resolveAnnotations/parameters/short.kt"); } + @TestMetadata("compiler/testData/resolveAnnotations/parameters/expressions") + public static class Expressions extends AbstractAnnotationParameterTest { + public void testAllFilesPresentInExpressions() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/resolveAnnotations/parameters/expressions"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("escapedString.kt") + public void testEscapedString() throws Exception { + doTest("compiler/testData/resolveAnnotations/parameters/expressions/escapedString.kt"); + } + + @TestMetadata("multilineString.kt") + public void testMultilineString() throws Exception { + doTest("compiler/testData/resolveAnnotations/parameters/expressions/multilineString.kt"); + } + + @TestMetadata("stringPlusInt.kt") + public void testStringPlusInt() throws Exception { + doTest("compiler/testData/resolveAnnotations/parameters/expressions/stringPlusInt.kt"); + } + + @TestMetadata("stringTemplate.kt") + public void testStringTemplate() throws Exception { + doTest("compiler/testData/resolveAnnotations/parameters/expressions/stringTemplate.kt"); + } + + @TestMetadata("strings.kt") + public void testStrings() throws Exception { + doTest("compiler/testData/resolveAnnotations/parameters/expressions/strings.kt"); + } + + } + + public static Test suite() { + TestSuite suite = new TestSuite("AnnotationParameterTestGenerated"); + suite.addTestSuite(AnnotationParameterTestGenerated.class); + suite.addTestSuite(Expressions.class); + return suite; + } }