AA FIR: constant evaluation for String
This commit is contained in:
committed by
Ilya Kirillov
parent
0d1b748cd5
commit
836b6dae9f
+36
@@ -189,4 +189,40 @@ public class Fe10IdeNormalAnalysisSourceModuleCompileTimeConstantEvaluatorTestGe
|
||||
public void testStringLiteral() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/stringLiteral.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_compareTo.kt")
|
||||
public void testString_compareTo() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_compareTo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_length.kt")
|
||||
public void testString_length() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_length.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusMany.kt")
|
||||
public void testString_plusMany() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusMany.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusOnce.kt")
|
||||
public void testString_plusOnce() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusOnce.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusTwice.kt")
|
||||
public void testString_plusTwice() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusTwice.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_toString.kt")
|
||||
public void testString_toString() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_toString.kt");
|
||||
}
|
||||
}
|
||||
|
||||
+52
-3
@@ -28,6 +28,8 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.fir.types.impl.*
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.StandardClassIds
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.CompileTimeType
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.evalBinaryOp
|
||||
@@ -47,7 +49,13 @@ internal object FirCompileTimeConstantEvaluator {
|
||||
when (fir) {
|
||||
is FirPropertyAccessExpression -> {
|
||||
when (val referredVariable = fir.referredVariableSymbol) {
|
||||
is FirPropertySymbol -> referredVariable.toConstExpression(mode)
|
||||
is FirPropertySymbol -> {
|
||||
if (referredVariable.callableId.isStringLength) {
|
||||
evaluate(fir.explicitReceiver, mode)?.evaluateStringLength()
|
||||
} else {
|
||||
referredVariable.toConstExpression(mode)
|
||||
}
|
||||
}
|
||||
is FirFieldSymbol -> referredVariable.toConstExpression(mode)
|
||||
else -> null
|
||||
}
|
||||
@@ -67,6 +75,9 @@ internal object FirCompileTimeConstantEvaluator {
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val CallableId.isStringLength: Boolean
|
||||
get() = classId == StandardClassIds.String && callableName.identifierOrNullIfSpecial == "length"
|
||||
|
||||
private fun FirPropertySymbol.toConstExpression(
|
||||
mode: KtConstantEvaluationMode,
|
||||
): FirConstExpression<*>? {
|
||||
@@ -172,7 +183,15 @@ internal object FirCompileTimeConstantEvaluator {
|
||||
// Unary operators
|
||||
private fun FirConstExpression<*>.evaluate(function: FirSimpleFunction): FirConstExpression<*>? {
|
||||
if (value == null) return null
|
||||
// TODO: there are a couple operations on String, such as .length and .toString
|
||||
(value as? String)?.let { opr ->
|
||||
evalUnaryOp(
|
||||
function.name.asString(),
|
||||
kind.toCompileTimeType(),
|
||||
opr
|
||||
)?.let {
|
||||
return it.toConstantValueKind().toConstExpression(source, it)
|
||||
}
|
||||
}
|
||||
return kind.convertToNumber(value as? Number)?.let { opr ->
|
||||
evalUnaryOp(
|
||||
function.name.asString(),
|
||||
@@ -184,13 +203,37 @@ internal object FirCompileTimeConstantEvaluator {
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirConstExpression<*>.evaluateStringLength(): FirConstExpression<*>? {
|
||||
return (value as? String)?.length?.let {
|
||||
it.toConstantValueKind().toConstExpression(source, it)
|
||||
}
|
||||
}
|
||||
|
||||
// Binary operators
|
||||
private fun FirConstExpression<*>.evaluate(
|
||||
function: FirSimpleFunction,
|
||||
other: FirConstExpression<*>
|
||||
): FirConstExpression<*>? {
|
||||
if (value == null || other.value == null) return null
|
||||
// TODO: there are a couple operations on Strings, such as .compareTo, .equals, or .plus
|
||||
// NB: some utils accept very general types, and due to the way operation map works, we should up-cast rhs type.
|
||||
val rightType = when {
|
||||
function.symbol.callableId.isStringEquals -> CompileTimeType.ANY
|
||||
function.symbol.callableId.isStringPlus -> CompileTimeType.ANY
|
||||
else -> other.kind.toCompileTimeType()
|
||||
}
|
||||
(value as? String)?.let { opr1 ->
|
||||
other.value?.let { opr2 ->
|
||||
evalBinaryOp(
|
||||
function.name.asString(),
|
||||
kind.toCompileTimeType(),
|
||||
opr1,
|
||||
rightType,
|
||||
opr2
|
||||
)?.let {
|
||||
return it.toConstantValueKind().toConstExpression(source, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return kind.convertToNumber(value as? Number)?.let { opr1 ->
|
||||
other.kind.convertToNumber(other.value as? Number)?.let { opr2 ->
|
||||
evalBinaryOp(
|
||||
@@ -206,6 +249,12 @@ internal object FirCompileTimeConstantEvaluator {
|
||||
}
|
||||
}
|
||||
|
||||
private val CallableId.isStringEquals: Boolean
|
||||
get() = classId == StandardClassIds.String && callableName.identifierOrNullIfSpecial == "equals"
|
||||
|
||||
private val CallableId.isStringPlus: Boolean
|
||||
get() = classId == StandardClassIds.String && callableName.identifierOrNullIfSpecial == "plus"
|
||||
|
||||
////// KINDS
|
||||
|
||||
private fun FirTypeRef.toConstantValueKind(): ConstantValueKind<*>? =
|
||||
|
||||
+36
@@ -189,4 +189,40 @@ public class FirIdeDependentAnalysisSourceModuleCompileTimeConstantEvaluatorTest
|
||||
public void testStringLiteral() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/stringLiteral.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_compareTo.kt")
|
||||
public void testString_compareTo() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_compareTo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_length.kt")
|
||||
public void testString_length() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_length.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusMany.kt")
|
||||
public void testString_plusMany() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusMany.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusOnce.kt")
|
||||
public void testString_plusOnce() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusOnce.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusTwice.kt")
|
||||
public void testString_plusTwice() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusTwice.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_toString.kt")
|
||||
public void testString_toString() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_toString.kt");
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -189,4 +189,40 @@ public class FirIdeNormalAnalysisSourceModuleCompileTimeConstantEvaluatorTestGen
|
||||
public void testStringLiteral() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/stringLiteral.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_compareTo.kt")
|
||||
public void testString_compareTo() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_compareTo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_length.kt")
|
||||
public void testString_length() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_length.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusMany.kt")
|
||||
public void testString_plusMany() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusMany.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusOnce.kt")
|
||||
public void testString_plusOnce() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusOnce.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusTwice.kt")
|
||||
public void testString_plusTwice() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusTwice.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_toString.kt")
|
||||
public void testString_toString() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_toString.kt");
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -189,4 +189,40 @@ public class FirStandaloneNormalAnalysisSourceModuleCompileTimeConstantEvaluator
|
||||
public void testStringLiteral() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/stringLiteral.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_compareTo.kt")
|
||||
public void testString_compareTo() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_compareTo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_length.kt")
|
||||
public void testString_length() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_length.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusMany.kt")
|
||||
public void testString_plusMany() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusMany.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusOnce.kt")
|
||||
public void testString_plusOnce() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusOnce.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_plusTwice.kt")
|
||||
public void testString_plusTwice() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_plusTwice.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("string_toString.kt")
|
||||
public void testString_toString() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/compileTimeConstantProvider/evaluate/string_toString.kt");
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
fun box() {
|
||||
if (<expr>"a".compareTo("b")</expr> > 0)
|
||||
TODO("no way!")
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
expression: "a".compareTo("b")
|
||||
|
||||
CONSTANT_EXPRESSION_EVALUATION
|
||||
constant: -1
|
||||
constantValueKind: Int
|
||||
|
||||
CONSTANT_LIKE_EXPRESSION_EVALUATION
|
||||
constantLike: -1
|
||||
constantLikeValueKind: Int
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
const val LENGTH = <expr>"Hello, world".length</expr>
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
expression: "Hello, world".length
|
||||
|
||||
CONSTANT_EXPRESSION_EVALUATION
|
||||
constant: 12
|
||||
constantValueKind: Int
|
||||
|
||||
CONSTANT_LIKE_EXPRESSION_EVALUATION
|
||||
constantLike: 12
|
||||
constantLikeValueKind: Int
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
const val PASSWORD = <expr>"na" + "na" + "na" + "na" + "na" + "na" + "na" + "na " + "batman"</expr>
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
expression: "na" + "na" + "na" + "na" + "na" + "na" + "na" + "na " + "batman"
|
||||
|
||||
CONSTANT_EXPRESSION_EVALUATION
|
||||
constant: "nananananananana batman"
|
||||
constantValueKind: String
|
||||
|
||||
CONSTANT_LIKE_EXPRESSION_EVALUATION
|
||||
constantLike: "nananananananana batman"
|
||||
constantLikeValueKind: String
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
const val PASSWORD = <expr>"nananananananana " + "batman"</expr>
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
expression: "nananananananana " + "batman"
|
||||
|
||||
CONSTANT_EXPRESSION_EVALUATION
|
||||
constant: "nananananananana batman"
|
||||
constantValueKind: String
|
||||
|
||||
CONSTANT_LIKE_EXPRESSION_EVALUATION
|
||||
constantLike: "nananananananana batman"
|
||||
constantLikeValueKind: String
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
const val PASSWORD = <expr>"nananana" + "nananana " + "batman"</expr>
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
expression: "nananana" + "nananana " + "batman"
|
||||
|
||||
CONSTANT_EXPRESSION_EVALUATION
|
||||
constant: "nananananananana batman"
|
||||
constantValueKind: String
|
||||
|
||||
CONSTANT_LIKE_EXPRESSION_EVALUATION
|
||||
constantLike: "nananananananana batman"
|
||||
constantLikeValueKind: String
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
const val TAG = <expr>"Analysis API".toString()</expr>
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
expression: "Analysis API".toString()
|
||||
|
||||
CONSTANT_EXPRESSION_EVALUATION
|
||||
constant: "Analysis API"
|
||||
constantValueKind: String
|
||||
|
||||
CONSTANT_LIKE_EXPRESSION_EVALUATION
|
||||
constantLike: "Analysis API"
|
||||
constantLikeValueKind: String
|
||||
Reference in New Issue
Block a user