[IR] Create common interface for all interpreter checkers

This commit is contained in:
Ivan Kylchik
2023-04-28 11:21:34 +02:00
committed by Space Team
parent 82a111250b
commit c8371a339f
4 changed files with 99 additions and 84 deletions
@@ -0,0 +1,17 @@
/*
* 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.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
interface IrInterpreterChecker : IrElementVisitor<Boolean, IrInterpreterCheckerData> {
val mode: EvaluationMode
}
class IrInterpreterCheckerData(
val interpreterConfiguration: IrInterpreterConfiguration
)
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.interpreter.accessesTopLevelOrObjectField import org.jetbrains.kotlin.ir.interpreter.accessesTopLevelOrObjectField
import org.jetbrains.kotlin.ir.interpreter.fqName import org.jetbrains.kotlin.ir.interpreter.fqName
import org.jetbrains.kotlin.ir.interpreter.isAccessToNotNullableObject import org.jetbrains.kotlin.ir.interpreter.isAccessToNotNullableObject
@@ -17,12 +16,10 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.statements import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
class IrCompileTimeChecker( class IrInterpreterCommonChecker(
private val mode: EvaluationMode, override val mode: EvaluationMode
private val interpreterConfiguration: IrInterpreterConfiguration, ) : IrInterpreterChecker {
) : IrElementVisitor<Boolean, Nothing?> {
private val visitedStack = mutableListOf<IrElement>() private val visitedStack = mutableListOf<IrElement>()
private inline fun IrElement.asVisited(crossinline block: () -> Boolean): Boolean { private inline fun IrElement.asVisited(crossinline block: () -> Boolean): Boolean {
@@ -32,35 +29,35 @@ class IrCompileTimeChecker(
return result return result
} }
override fun visitElement(element: IrElement, data: Nothing?) = false override fun visitElement(element: IrElement, data: IrInterpreterCheckerData) = false
private fun IrDeclarationParent.getInnerDeclarations(): List<IrStatement> { private fun IrDeclarationParent.getInnerDeclarations(): List<IrStatement> {
return (this as? IrDeclarationContainer)?.declarations ?: (this as? IrStatementContainer)?.statements ?: emptyList() return (this as? IrDeclarationContainer)?.declarations ?: (this as? IrStatementContainer)?.statements ?: emptyList()
} }
private fun visitStatements(statements: List<IrStatement>): Boolean { private fun visitStatements(statements: List<IrStatement>, data: IrInterpreterCheckerData): Boolean {
return statements.all { it.accept(this, null) } return statements.all { it.accept(this, data) }
} }
private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean { private fun visitConstructor(expression: IrFunctionAccessExpression, data: IrInterpreterCheckerData): Boolean {
val constructor = expression.symbol.owner val constructor = expression.symbol.owner
if (!mode.canEvaluateFunction(constructor)) return false if (!mode.canEvaluateFunction(constructor)) return false
if (!visitValueArguments(expression, null)) return false if (!visitValueArguments(expression, data)) return false
return constructor.visitBodyIfNeeded() && return visitBodyIfNeeded(constructor, data) &&
constructor.parentAsClass.declarations.filterIsInstance<IrAnonymousInitializer>().all { it.accept(this, null) } constructor.parentAsClass.declarations.filterIsInstance<IrAnonymousInitializer>().all { it.accept(this, data) }
} }
private fun IrFunction.visitBodyIfNeeded(): Boolean { private fun visitBodyIfNeeded(irFunction: IrFunction, data: IrInterpreterCheckerData): Boolean {
if (!mode.mustCheckBodyOf(this)) return true if (!mode.mustCheckBodyOf(irFunction)) return true
return this.asVisited { this.body?.accept(this@IrCompileTimeChecker, null) ?: true } return irFunction.asVisited { irFunction.body?.accept(this@IrInterpreterCommonChecker, data) ?: true }
} }
private fun IrCall.isGetterToConstVal(): Boolean { private fun IrCall.isGetterToConstVal(): Boolean {
return symbol.owner.correspondingPropertySymbol?.owner?.isConst == true return symbol.owner.correspondingPropertySymbol?.owner?.isConst == true
} }
override fun visitCall(expression: IrCall, data: Nothing?): Boolean { override fun visitCall(expression: IrCall, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false if (!mode.canEvaluateExpression(expression)) return false
val owner = expression.symbol.owner val owner = expression.symbol.owner
@@ -69,7 +66,7 @@ class IrCompileTimeChecker(
// We disable `toFloat` folding on K/JS till `toFloat` is fixed (KT-35422) // 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 // This check must be placed here instead of CallInterceptor because we still
// want to evaluate (1) `const val` expressions and (2) values in annotations. // want to evaluate (1) `const val` expressions and (2) values in annotations.
if (owner.name.asString() == "toFloat" && interpreterConfiguration.treatFloatInSpecialWay) { if (owner.name.asString() == "toFloat" && data.interpreterConfiguration.treatFloatInSpecialWay) {
return super.visitCall(expression, data) return super.visitCall(expression, data)
} }
@@ -77,33 +74,33 @@ class IrCompileTimeChecker(
return expression.isGetterToConstVal() return expression.isGetterToConstVal()
} }
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
if (!visitValueArguments(expression, null)) return false if (!visitValueArguments(expression, data)) return false
val bodyComputable = owner.visitBodyIfNeeded() val bodyComputable = visitBodyIfNeeded(owner, data)
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
} }
override fun visitVariable(declaration: IrVariable, data: Nothing?): Boolean { override fun visitVariable(declaration: IrVariable, data: IrInterpreterCheckerData): Boolean {
return declaration.initializer?.accept(this, data) ?: true return declaration.initializer?.accept(this, data) ?: true
} }
private fun visitValueArguments(expression: IrFunctionAccessExpression, data: Nothing?): Boolean { private fun visitValueArguments(expression: IrFunctionAccessExpression, data: IrInterpreterCheckerData): Boolean {
return (0 until expression.valueArgumentsCount) return (0 until expression.valueArgumentsCount)
.map { expression.getValueArgument(it) } .map { expression.getValueArgument(it) }
.none { it?.accept(this, data) == false } .none { it?.accept(this, data) == false }
} }
override fun visitBody(body: IrBody, data: Nothing?): Boolean { override fun visitBody(body: IrBody, data: IrInterpreterCheckerData): Boolean {
return visitStatements(body.statements) return visitStatements(body.statements, data)
} }
// We need this separate method to explicitly indicate that IrExpressionBody can be interpreted in any evaluation mode // We need this separate method to explicitly indicate that IrExpressionBody can be interpreted in any evaluation mode
override fun visitExpressionBody(body: IrExpressionBody, data: Nothing?): Boolean { override fun visitExpressionBody(body: IrExpressionBody, data: IrInterpreterCheckerData): Boolean {
return body.expression.accept(this, data) return body.expression.accept(this, data)
} }
override fun visitBlock(expression: IrBlock, data: Nothing?): Boolean { override fun visitBlock(expression: IrBlock, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateBlock(expression)) return false if (!mode.canEvaluateBlock(expression)) return false
// `IrReturnableBlock` will be created from IrCall after inline. We should do basically the same check as for IrCall. // `IrReturnableBlock` will be created from IrCall after inline. We should do basically the same check as for IrCall.
@@ -112,20 +109,20 @@ class IrCompileTimeChecker(
if (inlinedBlock != null) return inlinedBlock.inlineCall.accept(this, data) if (inlinedBlock != null) return inlinedBlock.inlineCall.accept(this, data)
} }
return visitStatements(expression.statements) return visitStatements(expression.statements, data)
} }
override fun visitComposite(expression: IrComposite, data: Nothing?): Boolean { override fun visitComposite(expression: IrComposite, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateComposite(expression)) return false if (!mode.canEvaluateComposite(expression)) return false
return visitStatements(expression.statements) return visitStatements(expression.statements, data)
} }
override fun visitSyntheticBody(body: IrSyntheticBody, data: Nothing?): Boolean { override fun visitSyntheticBody(body: IrSyntheticBody, data: IrInterpreterCheckerData): Boolean {
return body.kind == IrSyntheticBodyKind.ENUM_VALUES || body.kind == IrSyntheticBodyKind.ENUM_VALUEOF return body.kind == IrSyntheticBodyKind.ENUM_VALUES || body.kind == IrSyntheticBodyKind.ENUM_VALUEOF
} }
override fun visitConst(expression: IrConst<*>, data: Nothing?): Boolean { override fun visitConst(expression: IrConst<*>, data: IrInterpreterCheckerData): Boolean {
if (expression.type.getUnsignedType() != null) { if (expression.type.getUnsignedType() != null) {
val constructor = expression.type.classOrNull?.owner?.constructors?.singleOrNull() ?: return false val constructor = expression.type.classOrNull?.owner?.constructors?.singleOrNull() ?: return false
return mode.canEvaluateFunction(constructor) return mode.canEvaluateFunction(constructor)
@@ -133,15 +130,15 @@ class IrCompileTimeChecker(
return true return true
} }
override fun visitVararg(expression: IrVararg, data: Nothing?): Boolean { override fun visitVararg(expression: IrVararg, data: IrInterpreterCheckerData): Boolean {
return expression.elements.any { it.accept(this, data) } return expression.elements.any { it.accept(this, data) }
} }
override fun visitSpreadElement(spread: IrSpreadElement, data: Nothing?): Boolean { override fun visitSpreadElement(spread: IrSpreadElement, data: IrInterpreterCheckerData): Boolean {
return spread.expression.accept(this, data) return spread.expression.accept(this, data)
} }
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean { override fun visitStringConcatenation(expression: IrStringConcatenation, data: IrInterpreterCheckerData): Boolean {
return expression.arguments.all { arg -> return expression.arguments.all { arg ->
when (arg) { when (arg) {
is IrGetObjectValue -> { is IrGetObjectValue -> {
@@ -149,7 +146,7 @@ class IrCompileTimeChecker(
.filterIsInstance<IrSimpleFunction>() .filterIsInstance<IrSimpleFunction>()
.single { it.name.asString() == "toString" && it.valueParameters.isEmpty() && it.extensionReceiverParameter == null } .single { it.name.asString() == "toString" && it.valueParameters.isEmpty() && it.extensionReceiverParameter == null }
mode.canEvaluateFunction(toString) && toString.visitBodyIfNeeded() mode.canEvaluateFunction(toString) && visitBodyIfNeeded(toString, data)
} }
else -> arg.accept(this, data) else -> arg.accept(this, data)
@@ -157,12 +154,12 @@ class IrCompileTimeChecker(
} }
} }
override fun visitGetObjectValue(expression: IrGetObjectValue, data: Nothing?): Boolean { override fun visitGetObjectValue(expression: IrGetObjectValue, data: IrInterpreterCheckerData): Boolean {
// to get object value we need nothing, but it will contain only fields with compile time annotation // to get object value we need nothing, but it will contain only fields with compile time annotation
return true return true
} }
override fun visitGetEnumValue(expression: IrGetEnumValue, data: Nothing?): Boolean { override fun visitGetEnumValue(expression: IrGetEnumValue, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateEnumValue(expression)) 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) }" // we want to avoid recursion in cases like "enum class E(val srt: String) { OK(OK.name) }"
@@ -172,15 +169,15 @@ class IrCompileTimeChecker(
} }
} }
override fun visitGetValue(expression: IrGetValue, data: Nothing?): Boolean { override fun visitGetValue(expression: IrGetValue, data: IrInterpreterCheckerData): Boolean {
return visitedStack.contains(expression.symbol.owner.parent) return visitedStack.contains(expression.symbol.owner.parent)
} }
override fun visitSetValue(expression: IrSetValue, data: Nothing?): Boolean { override fun visitSetValue(expression: IrSetValue, data: IrInterpreterCheckerData): Boolean {
return expression.value.accept(this, data) return expression.value.accept(this, data)
} }
override fun visitGetField(expression: IrGetField, data: Nothing?): Boolean { override fun visitGetField(expression: IrGetField, data: IrInterpreterCheckerData): Boolean {
val owner = expression.symbol.owner val owner = expression.symbol.owner
val property = owner.correspondingPropertySymbol?.owner val property = owner.correspondingPropertySymbol?.owner
val fqName = owner.fqName val fqName = owner.fqName
@@ -194,11 +191,11 @@ class IrCompileTimeChecker(
// its type is flexible (so its not primitive) and there is no initializer at backing field // its type is flexible (so its not primitive) and there is no initializer at backing field
fqName == "java.lang.Boolean.FALSE" || fqName == "java.lang.Boolean.TRUE" -> true fqName == "java.lang.Boolean.FALSE" || fqName == "java.lang.Boolean.TRUE" -> true
isJavaStaticWithPrimitiveOrString() -> owner.initializer?.accept(this, data) == true isJavaStaticWithPrimitiveOrString() -> owner.initializer?.accept(this, data) == true
expression.receiver == null -> property?.isConst == true && owner.initializer?.accept(this, null) == true expression.receiver == null -> property?.isConst == true && owner.initializer?.accept(this, data) == true
owner.origin == IrDeclarationOrigin.PROPERTY_BACKING_FIELD && property?.isConst == true -> { owner.origin == IrDeclarationOrigin.PROPERTY_BACKING_FIELD && property?.isConst == true -> {
val receiverComputable = (expression.receiver?.accept(this, null) ?: true) val receiverComputable = (expression.receiver?.accept(this, data) ?: true)
|| expression.isAccessToNotNullableObject() || expression.isAccessToNotNullableObject()
val initializerComputable = owner.initializer?.accept(this, null) ?: false val initializerComputable = owner.initializer?.accept(this, data) ?: false
receiverComputable && initializerComputable receiverComputable && initializerComputable
} }
else -> { else -> {
@@ -210,7 +207,7 @@ class IrCompileTimeChecker(
} }
} }
override fun visitSetField(expression: IrSetField, data: Nothing?): Boolean { override fun visitSetField(expression: IrSetField, data: IrInterpreterCheckerData): Boolean {
if (expression.accessesTopLevelOrObjectField()) return false if (expression.accessesTopLevelOrObjectField()) return false
//todo check receiver? //todo check receiver?
val property = expression.symbol.owner.correspondingPropertySymbol?.owner val property = expression.symbol.owner.correspondingPropertySymbol?.owner
@@ -219,20 +216,20 @@ class IrCompileTimeChecker(
return visitedStack.contains(setter) && expression.value.accept(this, data) return visitedStack.contains(setter) && expression.value.accept(this, data)
} }
override fun visitConstructorCall(expression: IrConstructorCall, data: Nothing?): Boolean { override fun visitConstructorCall(expression: IrConstructorCall, data: IrInterpreterCheckerData): Boolean {
return visitConstructor(expression) return visitConstructor(expression, data)
} }
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: Nothing?): Boolean { override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: IrInterpreterCheckerData): Boolean {
if (expression.symbol.owner.returnType.isAny()) return true if (expression.symbol.owner.returnType.isAny()) return true
return visitConstructor(expression) return visitConstructor(expression, data)
} }
override fun visitEnumConstructorCall(expression: IrEnumConstructorCall, data: Nothing?): Boolean { override fun visitEnumConstructorCall(expression: IrEnumConstructorCall, data: IrInterpreterCheckerData): Boolean {
return visitConstructor(expression) return visitConstructor(expression, data)
} }
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, data: Nothing?): Boolean { override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, data: IrInterpreterCheckerData): Boolean {
val irClass = expression.classSymbol.owner val irClass = expression.classSymbol.owner
val classProperties = irClass.declarations.filterIsInstance<IrProperty>() val classProperties = irClass.declarations.filterIsInstance<IrProperty>()
val anonymousInitializer = irClass.declarations.filterIsInstance<IrAnonymousInitializer>().filter { !it.isStatic } val anonymousInitializer = irClass.declarations.filterIsInstance<IrAnonymousInitializer>().filter { !it.isStatic }
@@ -244,27 +241,27 @@ class IrCompileTimeChecker(
} }
} }
override fun visitFunctionReference(expression: IrFunctionReference, data: Nothing?): Boolean { override fun visitFunctionReference(expression: IrFunctionReference, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateCallableReference(expression)) return false if (!mode.canEvaluateCallableReference(expression)) return false
val owner = expression.symbol.owner val owner = expression.symbol.owner
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
if (!mode.canEvaluateFunction(owner)) return false if (!mode.canEvaluateFunction(owner)) return false
val bodyComputable = owner.visitBodyIfNeeded() val bodyComputable = visitBodyIfNeeded(owner, data)
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
} }
override fun visitFunctionExpression(expression: IrFunctionExpression, data: Nothing?): Boolean { override fun visitFunctionExpression(expression: IrFunctionExpression, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateFunctionExpression(expression)) return false if (!mode.canEvaluateFunctionExpression(expression)) return false
val body = expression.function.body ?: return false val body = expression.function.body ?: return false
return expression.function.asVisited { body.accept(this, data) } return expression.function.asVisited { body.accept(this, data) }
} }
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: Nothing?): Boolean { override fun visitTypeOperator(expression: IrTypeOperatorCall, data: IrInterpreterCheckerData): Boolean {
return when (expression.operator) { return when (expression.operator) {
IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF, IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF,
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT, IrTypeOperator.IMPLICIT_NOTNULL, IrTypeOperator.SAM_CONVERSION, IrTypeOperator.IMPLICIT_COERCION_TO_UNIT, IrTypeOperator.IMPLICIT_NOTNULL, IrTypeOperator.SAM_CONVERSION,
@@ -277,29 +274,29 @@ class IrCompileTimeChecker(
} }
} }
override fun visitWhen(expression: IrWhen, data: Nothing?): Boolean { override fun visitWhen(expression: IrWhen, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false if (!mode.canEvaluateExpression(expression)) return false
return expression.branches.all { it.accept(this, data) } return expression.branches.all { it.accept(this, data) }
} }
override fun visitBranch(branch: IrBranch, data: Nothing?): Boolean { override fun visitBranch(branch: IrBranch, data: IrInterpreterCheckerData): Boolean {
return branch.condition.accept(this, data) && branch.result.accept(this, data) return branch.condition.accept(this, data) && branch.result.accept(this, data)
} }
override fun visitWhileLoop(loop: IrWhileLoop, data: Nothing?): Boolean { override fun visitWhileLoop(loop: IrWhileLoop, data: IrInterpreterCheckerData): Boolean {
return loop.asVisited { return loop.asVisited {
loop.condition.accept(this, data) && (loop.body?.accept(this, data) ?: true) loop.condition.accept(this, data) && (loop.body?.accept(this, data) ?: true)
} }
} }
override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: Nothing?): Boolean { override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: IrInterpreterCheckerData): Boolean {
return loop.asVisited { return loop.asVisited {
loop.condition.accept(this, data) && (loop.body?.accept(this, data) ?: true) loop.condition.accept(this, data) && (loop.body?.accept(this, data) ?: true)
} }
} }
override fun visitTry(aTry: IrTry, data: Nothing?): Boolean { override fun visitTry(aTry: IrTry, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(aTry)) return false if (!mode.canEvaluateExpression(aTry)) return false
if (!aTry.tryResult.accept(this, data)) return false if (!aTry.tryResult.accept(this, data)) return false
@@ -307,32 +304,32 @@ class IrCompileTimeChecker(
return aTry.catches.all { it.result.accept(this, data) } return aTry.catches.all { it.result.accept(this, data) }
} }
override fun visitBreak(jump: IrBreak, data: Nothing?): Boolean = visitedStack.contains(jump.loop) override fun visitBreak(jump: IrBreak, data: IrInterpreterCheckerData): Boolean = visitedStack.contains(jump.loop)
override fun visitContinue(jump: IrContinue, data: Nothing?): Boolean = visitedStack.contains(jump.loop) override fun visitContinue(jump: IrContinue, data: IrInterpreterCheckerData): Boolean = visitedStack.contains(jump.loop)
override fun visitReturn(expression: IrReturn, data: Nothing?): Boolean { override fun visitReturn(expression: IrReturn, data: IrInterpreterCheckerData): Boolean {
if (!visitedStack.contains(expression.returnTargetSymbol.owner)) return false if (!visitedStack.contains(expression.returnTargetSymbol.owner)) return false
return expression.value.accept(this, data) return expression.value.accept(this, data)
} }
override fun visitThrow(expression: IrThrow, data: Nothing?): Boolean { override fun visitThrow(expression: IrThrow, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false if (!mode.canEvaluateExpression(expression)) return false
return expression.value.accept(this, data) return expression.value.accept(this, data)
} }
override fun visitPropertyReference(expression: IrPropertyReference, data: Nothing?): Boolean { override fun visitPropertyReference(expression: IrPropertyReference, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateCallableReference(expression)) return false if (!mode.canEvaluateCallableReference(expression)) return false
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner) } ?: true val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner) } ?: true
return dispatchReceiverComputable && extensionReceiverComputable && getterIsComputable return dispatchReceiverComputable && extensionReceiverComputable && getterIsComputable
} }
override fun visitClassReference(expression: IrClassReference, data: Nothing?): Boolean { override fun visitClassReference(expression: IrClassReference, data: IrInterpreterCheckerData): Boolean {
return mode.canEvaluateClassReference(expression) return mode.canEvaluateClassReference(expression)
} }
} }
@@ -8,11 +8,10 @@ package org.jetbrains.kotlin.ir.interpreter.checker
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
class IrCompileTimeNameChecker( class IrInterpreterNameChecker(
private val mode: EvaluationMode override val mode: EvaluationMode
) : IrElementVisitor<Boolean, Nothing?> { ) : IrInterpreterChecker {
private fun IrCall.isIntrinsicConstEvaluationNameProperty(): Boolean { private fun IrCall.isIntrinsicConstEvaluationNameProperty(): Boolean {
val owner = this.symbol.owner val owner = this.symbol.owner
if (owner.extensionReceiverParameter != null || owner.valueParameters.isNotEmpty()) return false if (owner.extensionReceiverParameter != null || owner.valueParameters.isNotEmpty()) return false
@@ -20,9 +19,9 @@ class IrCompileTimeNameChecker(
return mode.canEvaluateFunction(owner) && property.name.asString() == "name" return mode.canEvaluateFunction(owner) && property.name.asString() == "name"
} }
override fun visitElement(element: IrElement, data: Nothing?) = false override fun visitElement(element: IrElement, data: IrInterpreterCheckerData) = false
override fun visitCall(expression: IrCall, data: Nothing?): Boolean { override fun visitCall(expression: IrCall, data: IrInterpreterCheckerData): Boolean {
if (!expression.isIntrinsicConstEvaluationNameProperty()) return false if (!expression.isIntrinsicConstEvaluationNameProperty()) return false
return when (val receiver = expression.dispatchReceiver) { return when (val receiver = expression.dispatchReceiver) {
is IrCallableReference<*> -> (receiver.dispatchReceiver == null || receiver.dispatchReceiver is IrGetObjectValue) && receiver.extensionReceiver == null is IrCallableReference<*> -> (receiver.dispatchReceiver == null || receiver.dispatchReceiver is IrGetObjectValue) && receiver.extensionReceiver == null
@@ -31,7 +30,7 @@ class IrCompileTimeNameChecker(
} }
} }
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean { override fun visitStringConcatenation(expression: IrStringConcatenation, data: IrInterpreterCheckerData): Boolean {
val possibleNameCall = expression.arguments.singleOrNull() as? IrCall ?: return false val possibleNameCall = expression.arguments.singleOrNull() as? IrCall ?: return false
return possibleNameCall.accept(this, data) return possibleNameCall.accept(this, data)
} }
@@ -16,9 +16,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode import org.jetbrains.kotlin.ir.interpreter.checker.*
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.interpreter.toConstantValue
import org.jetbrains.kotlin.ir.util.dump import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
@@ -56,6 +54,11 @@ internal abstract class IrConstTransformer(
private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit, private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
private val suppressExceptions: Boolean, private val suppressExceptions: Boolean,
) : IrElementTransformer<Nothing?> { ) : IrElementTransformer<Nothing?> {
private val checkers = setOf(
IrInterpreterCommonChecker(mode),
IrInterpreterNameChecker(mode)
)
private fun IrExpression.warningIfError(original: IrExpression): IrExpression { private fun IrExpression.warningIfError(original: IrExpression): IrExpression {
if (this is IrErrorExpression) { if (this is IrErrorExpression) {
onWarning(irFile, original, this) onWarning(irFile, original, this)
@@ -80,8 +83,7 @@ internal abstract class IrConstTransformer(
configuration: IrInterpreterConfiguration = interpreter.environment.configuration configuration: IrInterpreterConfiguration = interpreter.environment.configuration
): Boolean { ): Boolean {
return try { return try {
this.accept(IrCompileTimeChecker(mode, configuration), null) || checkers.any { this.accept(it, IrInterpreterCheckerData(configuration)) }
this.accept(IrCompileTimeNameChecker(mode), null)
} catch (e: Throwable) { } catch (e: Throwable) {
if (suppressExceptions) { if (suppressExceptions) {
return false return false