[IR] Create special checker that will analyze name methods

We can insert all this logic into `IrCompileTimeChecker` but it is
a little bit specific and looks like it is nicer to just extract it.
This commit is contained in:
Ivan Kylchik
2023-04-27 17:41:33 +02:00
committed by Space Team
parent 104ac4bd69
commit 1fd8ef801e
9 changed files with 130 additions and 54 deletions
@@ -28510,6 +28510,12 @@ public class FirLightTreeBlackBoxCodegenTestGenerated extends AbstractFirLightTr
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableName.kt");
}
@Test
@TestMetadata("kCallableNameWithSideEffect.kt")
public void testKCallableNameWithSideEffect() throws Exception {
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt");
}
@Test
@TestMetadata("kt53272.kt")
public void testKt53272() throws Exception {
@@ -28510,6 +28510,12 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableName.kt");
}
@Test
@TestMetadata("kCallableNameWithSideEffect.kt")
public void testKCallableNameWithSideEffect() throws Exception {
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt");
}
@Test
@TestMetadata("kt53272.kt")
public void testKt53272() throws Exception {
@@ -20,10 +20,10 @@ import org.jetbrains.kotlin.name.Name
enum class EvaluationMode {
FULL {
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean = true
override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean = true
override fun canEvaluateFunctionExpression(expression: IrFunctionExpression, context: IrCall?): Boolean = true
override fun canEvaluateCallableReference(reference: IrCallableReference<*>, context: IrCall?): Boolean = true
override fun canEvaluateFunction(function: IrFunction): Boolean = true
override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue): Boolean = true
override fun canEvaluateFunctionExpression(expression: IrFunctionExpression): Boolean = true
override fun canEvaluateCallableReference(reference: IrCallableReference<*>): Boolean = true
override fun canEvaluateClassReference(reference: IrDeclarationReference): Boolean = true
override fun canEvaluateBlock(block: IrBlock): Boolean = true
@@ -54,7 +54,7 @@ enum class EvaluationMode {
BuiltInOperatorNames.ANDAND, BuiltInOperatorNames.OROR
).map { IrBuiltIns.KOTLIN_INTERNAL_IR_FQN.child(Name.identifier(it)).asString() }.toSet()
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean {
override fun canEvaluateFunction(function: IrFunction): Boolean {
if (function.property?.isConst == true) return true
val returnType = function.returnType
@@ -78,10 +78,8 @@ enum class EvaluationMode {
},
ONLY_INTRINSIC_CONST {
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean {
return function.isCompileTimePropertyAccessor() ||
function.isMarkedAsIntrinsicConstEvaluation() ||
context.isIntrinsicConstEvaluationNameProperty()
override fun canEvaluateFunction(function: IrFunction): Boolean {
return function.isCompileTimePropertyAccessor() || function.isMarkedAsIntrinsicConstEvaluation()
}
private fun IrFunction?.isCompileTimePropertyAccessor(): Boolean {
@@ -89,29 +87,14 @@ enum class EvaluationMode {
return property.isConst || (property.resolveFakeOverride() ?: property).isMarkedAsIntrinsicConstEvaluation()
}
override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean {
return context.isIntrinsicConstEvaluationNameProperty()
}
override fun canEvaluateCallableReference(reference: IrCallableReference<*>, context: IrCall?): Boolean {
return context.isIntrinsicConstEvaluationNameProperty()
}
override fun canEvaluateBlock(block: IrBlock): Boolean = block.origin == IrStatementOrigin.WHEN || block.statements.size == 1
override fun canEvaluateExpression(expression: IrExpression): Boolean = expression is IrCall || expression is IrWhen
private fun IrCall?.isIntrinsicConstEvaluationNameProperty(): Boolean {
if (this == null) return false
val owner = this.symbol.owner
val property = owner.property ?: return false
return owner.isCompileTimePropertyAccessor() && property.name.asString() == "name"
}
};
open fun canEvaluateFunction(function: IrFunction, context: IrCall? = null): Boolean = false
open fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall? = null): Boolean = false
open fun canEvaluateFunctionExpression(expression: IrFunctionExpression, context: IrCall? = null): Boolean = false
open fun canEvaluateCallableReference(reference: IrCallableReference<*>, context: IrCall? = null): Boolean = false
open fun canEvaluateFunction(function: IrFunction): Boolean = false
open fun canEvaluateEnumValue(enumEntry: IrGetEnumValue): Boolean = false
open fun canEvaluateFunctionExpression(expression: IrFunctionExpression): Boolean = false
open fun canEvaluateCallableReference(reference: IrCallableReference<*>): Boolean = false
open fun canEvaluateClassReference(reference: IrDeclarationReference): Boolean = false
open fun canEvaluateBlock(block: IrBlock): Boolean = false
@@ -121,7 +104,9 @@ enum class EvaluationMode {
open fun canEvaluateExpression(expression: IrExpression): Boolean = false
open fun mustCheckBodyOf(function: IrFunction): Boolean = false
open fun mustCheckBodyOf(function: IrFunction): Boolean {
return function.property != null
}
protected fun IrDeclaration.isMarkedAsIntrinsicConstEvaluation() = isMarkedWith(intrinsicConstEvaluationAnnotation)
@@ -24,7 +24,6 @@ class IrCompileTimeChecker(
private val mode: EvaluationMode,
private val interpreterConfiguration: IrInterpreterConfiguration,
) : IrElementVisitor<Boolean, Nothing?> {
private var contextExpression: IrCall? = null
private val visitedStack = mutableListOf<IrElement>().apply { if (containingDeclaration != null) add(containingDeclaration) }
private inline fun IrElement.asVisited(crossinline block: () -> Boolean): Boolean {
@@ -34,12 +33,6 @@ class IrCompileTimeChecker(
return result
}
private inline fun <R> IrCall.saveContext(crossinline block: () -> R): R {
val oldContext = contextExpression
contextExpression = this
return block().apply { contextExpression = oldContext }
}
override fun visitElement(element: IrElement, data: Nothing?) = false
private fun IrDeclarationParent.getInnerDeclarations(): List<IrStatement> {
@@ -53,7 +46,7 @@ class IrCompileTimeChecker(
private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean {
val constructor = expression.symbol.owner
if (!mode.canEvaluateFunction(constructor, contextExpression)) return false
if (!mode.canEvaluateFunction(constructor)) return false
if (!visitValueArguments(expression, null)) return false
return constructor.visitBodyIfNeeded() &&
constructor.parentAsClass.declarations.filterIsInstance<IrAnonymousInitializer>().all { it.accept(this, null) }
@@ -72,7 +65,7 @@ class IrCompileTimeChecker(
if (!mode.canEvaluateExpression(expression)) return false
val owner = expression.symbol.owner
if (!mode.canEvaluateFunction(owner, expression)) return false
if (!mode.canEvaluateFunction(owner)) return false
// We disable `toFloat` folding on K/JS till `toFloat` is fixed (KT-35422)
// This check must be placed here instead of CallInterceptor because we still
@@ -81,17 +74,15 @@ class IrCompileTimeChecker(
return super.visitCall(expression, data)
}
return expression.saveContext {
if (expression.dispatchReceiver.isAccessToNotNullableObject()) {
return@saveContext expression.isGetterToConstVal()
}
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!visitValueArguments(expression, null)) return@saveContext false
val bodyComputable = owner.visitBodyIfNeeded()
return@saveContext dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
if (expression.dispatchReceiver.isAccessToNotNullableObject()) {
return expression.isGetterToConstVal()
}
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!visitValueArguments(expression, null)) return false
val bodyComputable = owner.visitBodyIfNeeded()
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
}
override fun visitVariable(declaration: IrVariable, data: Nothing?): Boolean {
@@ -173,7 +164,7 @@ class IrCompileTimeChecker(
}
override fun visitGetEnumValue(expression: IrGetEnumValue, data: Nothing?): Boolean {
if (!mode.canEvaluateEnumValue(expression, contextExpression)) return false
if (!mode.canEvaluateEnumValue(expression)) return false
// we want to avoid recursion in cases like "enum class E(val srt: String) { OK(OK.name) }"
if (visitedStack.contains(expression)) return true
@@ -253,13 +244,13 @@ class IrCompileTimeChecker(
}
override fun visitFunctionReference(expression: IrFunctionReference, data: Nothing?): Boolean {
if (!mode.canEvaluateCallableReference(expression, contextExpression)) return false
if (!mode.canEvaluateCallableReference(expression)) return false
val owner = expression.symbol.owner
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!mode.canEvaluateFunction(owner, contextExpression)) return false
if (!mode.canEvaluateFunction(owner)) return false
val bodyComputable = owner.visitBodyIfNeeded()
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
@@ -331,12 +322,12 @@ class IrCompileTimeChecker(
}
override fun visitPropertyReference(expression: IrPropertyReference, data: Nothing?): Boolean {
if (!mode.canEvaluateCallableReference(expression, contextExpression)) return false
if (!mode.canEvaluateCallableReference(expression)) return false
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner, contextExpression) } ?: true
val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner) } ?: true
return dispatchReceiverComputable && extensionReceiverComputable && getterIsComputable
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.ir.interpreter.checker
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
class IrCompileTimeNameChecker(
private val mode: EvaluationMode
) : IrElementVisitor<Boolean, Nothing?> {
private fun IrCall.isIntrinsicConstEvaluationNameProperty(): Boolean {
val owner = this.symbol.owner
if (owner.extensionReceiverParameter != null || owner.valueParameters.isNotEmpty()) return false
val property = (owner as? IrSimpleFunction)?.correspondingPropertySymbol?.owner ?: return false
return mode.canEvaluateFunction(owner) && property.name.asString() == "name"
}
override fun visitElement(element: IrElement, data: Nothing?) = false
override fun visitCall(expression: IrCall, data: Nothing?): Boolean {
if (!expression.isIntrinsicConstEvaluationNameProperty()) return false
return when (val receiver = expression.dispatchReceiver) {
is IrCallableReference<*> -> (receiver.dispatchReceiver == null || receiver.dispatchReceiver is IrGetObjectValue) && receiver.extensionReceiver == null
is IrGetEnumValue -> true
else -> false
}
}
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean {
val possibleNameCall = expression.arguments.singleOrNull() as? IrCall ?: return false
return possibleNameCall.accept(this, data)
}
}
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.interpreter.checker.IrCompileTimeChecker
import org.jetbrains.kotlin.ir.interpreter.checker.IrCompileTimeNameChecker
import org.jetbrains.kotlin.ir.interpreter.toConstantValue
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
@@ -80,7 +81,8 @@ internal abstract class IrConstTransformer(
configuration: IrInterpreterConfiguration = interpreter.environment.configuration
): Boolean {
return try {
this.accept(IrCompileTimeChecker(containingDeclaration, mode, configuration), null)
this.accept(IrCompileTimeChecker(containingDeclaration, mode, configuration), null) ||
this.accept(IrCompileTimeNameChecker(mode), null)
} catch (e: Throwable) {
if (suppressExceptions) {
return false
@@ -0,0 +1,36 @@
// !LANGUAGE: +IntrinsicConstEvaluation
// TARGET_BACKEND: JVM_IR
// WITH_STDLIB
fun <T> T.id() = this
class A {
val a = ""
fun b() = ""
init {
println("A init")
}
fun test() {
val a = A::a.<!EVALUATED("a")!>name<!>
val b = A::b.<!EVALUATED("b")!>name<!>
val c = ::A.<!EVALUATED("<init>")!>name<!>
val d = this::a.name
val e = A()::b.name
val f = getA()::b.name
val temp = A()
val g = temp::b.name
}
fun getA(): A = A()
}
// STOP_EVALUATION_CHECKS
fun box(): String {
A().test()
return "OK"
}
@@ -28510,6 +28510,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableName.kt");
}
@Test
@TestMetadata("kCallableNameWithSideEffect.kt")
public void testKCallableNameWithSideEffect() throws Exception {
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt");
}
@Test
@TestMetadata("kt53272.kt")
public void testKt53272() throws Exception {
@@ -28510,6 +28510,12 @@ public class IrBlackBoxCodegenWithIrInlinerTestGenerated extends AbstractIrBlack
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableName.kt");
}
@Test
@TestMetadata("kCallableNameWithSideEffect.kt")
public void testKCallableNameWithSideEffect() throws Exception {
runTest("compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt");
}
@Test
@TestMetadata("kt53272.kt")
public void testKt53272() throws Exception {