Properly verify args in string concatenation expression for interpreter
If string concatenation contains object value, then this argument will not have explicit toString call. We must find and check toString method manually. #KT-54615
This commit is contained in:
+6
@@ -20495,6 +20495,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("stringConcatenationWithObject.kt")
|
||||
public void testStringConcatenationWithObject() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("types.kt")
|
||||
public void testTypes() throws Exception {
|
||||
|
||||
+6
@@ -20495,6 +20495,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("stringConcatenationWithObject.kt")
|
||||
public void testStringConcatenationWithObject() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("types.kt")
|
||||
public void testTypes() throws Exception {
|
||||
|
||||
+6
@@ -20495,6 +20495,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("stringConcatenationWithObject.kt")
|
||||
public void testStringConcatenationWithObject() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("types.kt")
|
||||
public void testTypes() throws Exception {
|
||||
|
||||
+5
-1
@@ -66,8 +66,12 @@ internal fun checkConstantArguments(
|
||||
return checkConstantArguments(expression.compareToCall, session)
|
||||
}
|
||||
expression is FirStringConcatenationCall || expression is FirEqualityOperatorCall -> {
|
||||
for (exp in (expression as FirCall).arguments)
|
||||
for (exp in (expression as FirCall).arguments) {
|
||||
if (exp is FirResolvedQualifier) {
|
||||
return ConstantArgumentKind.NOT_CONST
|
||||
}
|
||||
checkConstantArguments(exp, session).let { return it }
|
||||
}
|
||||
}
|
||||
expression is FirGetClassCall -> {
|
||||
var coneType = (expression as? FirCall)?.argument?.typeRef?.coneType
|
||||
|
||||
+6
@@ -17033,6 +17033,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/evaluate/simpleCallBinary.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("stringConcatenationWithObject.kt")
|
||||
public void testStringConcatenationWithObject() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/evaluate/stringConcatenationWithObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("thisPlusString.kt")
|
||||
public void testThisPlusString() throws Exception {
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) {
|
||||
abstract fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall? = null): Boolean
|
||||
abstract fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall? = null): Boolean
|
||||
|
||||
fun canEvaluateBody(function: IrFunction): Boolean {
|
||||
fun mustCheckBodyOf(function: IrFunction): Boolean {
|
||||
if (function is IrSimpleFunction && function.correspondingPropertySymbol != null) return true
|
||||
return (mustCheckBody || function.isLocal) && !function.isContract() && !function.isMarkedAsEvaluateIntrinsic()
|
||||
}
|
||||
|
||||
+20
-5
@@ -54,11 +54,14 @@ class IrCompileTimeChecker(
|
||||
private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean {
|
||||
return when {
|
||||
!visitValueParameters(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false
|
||||
mode.canEvaluateBody(expression.symbol.owner) -> expression.symbol.owner.body?.accept(this, null) != false
|
||||
else -> true
|
||||
else -> expression.symbol.owner.visitBodyIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrFunction.visitBodyIfNeeded(): Boolean {
|
||||
return this.asVisited { !mode.mustCheckBodyOf(this) || (this.body?.accept(this@IrCompileTimeChecker, null) ?: true) }
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: Nothing?): Boolean {
|
||||
val owner = expression.symbol.owner
|
||||
if (!mode.canEvaluateFunction(owner, expression)) return false
|
||||
@@ -67,7 +70,7 @@ class IrCompileTimeChecker(
|
||||
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
|
||||
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
|
||||
if (!visitValueParameters(expression, null)) return@saveContext false
|
||||
val bodyComputable = owner.asVisited { if (mode.canEvaluateBody(owner)) owner.body?.accept(this, null) ?: true else true }
|
||||
val bodyComputable = owner.visitBodyIfNeeded()
|
||||
return@saveContext dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
|
||||
}
|
||||
}
|
||||
@@ -126,7 +129,19 @@ class IrCompileTimeChecker(
|
||||
}
|
||||
|
||||
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean {
|
||||
return expression.arguments.all { it.accept(this, data) }
|
||||
return expression.arguments.all { arg ->
|
||||
when (arg) {
|
||||
is IrGetObjectValue -> {
|
||||
val toString = arg.symbol.owner.declarations
|
||||
.filterIsInstance<IrSimpleFunction>()
|
||||
.single { it.name.asString() == "toString" && it.valueParameters.isEmpty() }
|
||||
|
||||
mode.canEvaluateFunction(toString, null) && toString.visitBodyIfNeeded()
|
||||
}
|
||||
|
||||
else -> arg.accept(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitGetObjectValue(expression: IrGetObjectValue, data: Nothing?): Boolean {
|
||||
@@ -218,7 +233,7 @@ class IrCompileTimeChecker(
|
||||
|
||||
if (!mode.canEvaluateFunction(owner, contextExpression)) return false
|
||||
|
||||
val bodyComputable = owner.asVisited { if (mode.canEvaluateBody(owner)) owner.body?.accept(this, null) ?: true else true }
|
||||
val bodyComputable = owner.visitBodyIfNeeded()
|
||||
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -55,8 +55,10 @@ class IrConstTransformer(
|
||||
val result = try {
|
||||
interpreter.interpret(this, irFile)
|
||||
} catch (e: Throwable) {
|
||||
if (!suppressExceptions) throw AssertionError("Error occurred while optimizing an expression:\n${this.dump()}", e)
|
||||
return IrErrorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, this.type, e.message.toString()).warningIfError(this)
|
||||
if (suppressExceptions) {
|
||||
return this
|
||||
}
|
||||
throw AssertionError("Error occurred while optimizing an expression:\n${this.dump()}", e)
|
||||
}
|
||||
|
||||
return if (failAsError) result.reportIfError(this) else result.warningIfError(this)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
object K : Code("K")
|
||||
|
||||
open class Code(val x: String) {
|
||||
override fun toString() = "$x"
|
||||
}
|
||||
|
||||
class O {
|
||||
companion object: Code("O")
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
return "$O" + "$K" // must not be evaluated during compile time
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// FIR_IDENTICAL
|
||||
|
||||
object O : Code(0)
|
||||
|
||||
open class Code(val x: Int) {
|
||||
override fun toString() = "$x"
|
||||
}
|
||||
|
||||
class A {
|
||||
companion object: Code(0)
|
||||
}
|
||||
|
||||
const val toString1 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>O.toString()<!>
|
||||
const val toString2 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>A.toString()<!>
|
||||
const val plusString1 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"string" + O<!>
|
||||
const val plusString2 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"string" + A<!>
|
||||
const val stringConcat1 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"$O"<!>
|
||||
const val stringConcat2 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"$A"<!>
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package
|
||||
|
||||
public const val plusString1: kotlin.String
|
||||
public const val plusString2: kotlin.String
|
||||
public const val stringConcat1: kotlin.String
|
||||
public const val stringConcat2: kotlin.String
|
||||
public const val toString1: kotlin.String
|
||||
public const val toString2: kotlin.String
|
||||
|
||||
public final class A {
|
||||
public constructor A()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
public companion object Companion : Code {
|
||||
private constructor Companion()
|
||||
public final override /*1*/ /*fake_override*/ val x: kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
}
|
||||
|
||||
public open class Code {
|
||||
public constructor Code(/*0*/ x: kotlin.Int)
|
||||
public final val x: kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public object O : Code {
|
||||
private constructor O()
|
||||
public final override /*1*/ /*fake_override*/ val x: kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
Generated
+6
@@ -20501,6 +20501,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("stringConcatenationWithObject.kt")
|
||||
public void testStringConcatenationWithObject() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("types.kt")
|
||||
public void testTypes() throws Exception {
|
||||
|
||||
+6
@@ -17033,6 +17033,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/evaluate/simpleCallBinary.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("stringConcatenationWithObject.kt")
|
||||
public void testStringConcatenationWithObject() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/evaluate/stringConcatenationWithObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("thisPlusString.kt")
|
||||
public void testThisPlusString() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user