diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt index 90a5457e8ba..7b55b89faa3 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt @@ -35,7 +35,6 @@ import kotlin.math.floor val intrinsicConstEvaluationAnnotation = FqName("kotlin.internal.IntrinsicConstEvaluation") val compileTimeAnnotation = FqName("kotlin.CompileTimeCalculation") val evaluateIntrinsicAnnotation = FqName("kotlin.EvaluateIntrinsic") -val contractsDslAnnotation = FqName("kotlin.internal.ContractsDsl") internal val IrElement.fqName: String get() = (this as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "" diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt index 02b72f9c887..4db265f4bdc 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.interpreter.* import org.jetbrains.kotlin.ir.interpreter.hasAnnotation -import org.jetbrains.kotlin.ir.types.isAny import org.jetbrains.kotlin.ir.types.isPrimitiveType import org.jetbrains.kotlin.ir.types.isString import org.jetbrains.kotlin.ir.types.isUnsignedType @@ -19,14 +18,23 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -enum class EvaluationMode(protected val mustCheckBody: Boolean) { - FULL(mustCheckBody = true) { +enum class EvaluationMode { + FULL { override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean = true override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean = true - override fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall?): Boolean = true + override fun canEvaluateFunctionExpression(expression: IrFunctionExpression, context: IrCall?): Boolean = true + override fun canEvaluateCallableReference(reference: IrCallableReference<*>, context: IrCall?): Boolean = true + override fun canEvaluateClassReference(reference: IrDeclarationReference): Boolean = true + + override fun canEvaluateBlock(block: IrBlock): Boolean = true + override fun canEvaluateComposite(composite: IrComposite): Boolean = true + + override fun canEvaluateExpression(expression: IrExpression): Boolean = true + + override fun mustCheckBodyOf(function: IrFunction): Boolean = true }, - ONLY_BUILTINS(mustCheckBody = false) { + ONLY_BUILTINS { private val allowedMethodsOnPrimitives = setOf( "not", "unaryMinus", "unaryPlus", "inv", "toString", "toChar", "toByte", "toShort", "toInt", "toLong", "toFloat", "toDouble", @@ -59,20 +67,21 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) { parentType == null -> fqName in allowedExtensionFunctions || fqName in allowedBuiltinExtensionFunctions parentType.isPrimitiveType() -> function.name.asString() in allowedMethodsOnPrimitives parentType.isString() -> function.name.asString() in allowedMethodsOnStrings - parentType.isAny() -> function.name.asString() == "toString" && context?.dispatchReceiver !is IrGetObjectValue parent.isObject -> parent.parentClassOrNull?.defaultType?.let { it.isPrimitiveType() || it.isUnsigned() } == true parentType.isUnsignedType() && function is IrConstructor -> true else -> fqName in allowedExtensionFunctions || fqName in allowedBuiltinExtensionFunctions } } - override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean = false - override fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall?): Boolean = false + override fun canEvaluateBlock(block: IrBlock): Boolean = block.statements.size == 1 + override fun canEvaluateExpression(expression: IrExpression): Boolean = expression is IrCall }, - ONLY_INTRINSIC_CONST(mustCheckBody = false) { + ONLY_INTRINSIC_CONST { override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean { - return function.isCompileTimePropertyAccessor() || function.isMarkedAsIntrinsicConstEvaluation() || context.isIntrinsicConstEvaluationNameProperty() + return function.isCompileTimePropertyAccessor() || + function.isMarkedAsIntrinsicConstEvaluation() || + context.isIntrinsicConstEvaluationNameProperty() } private fun IrFunction?.isCompileTimePropertyAccessor(): Boolean { @@ -84,10 +93,13 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) { return context.isIntrinsicConstEvaluationNameProperty() } - override fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall?): Boolean { + 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 @@ -96,24 +108,22 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) { } }; - abstract fun canEvaluateFunction(function: IrFunction, context: IrCall? = null): Boolean - abstract fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall? = null): Boolean - abstract fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall? = null): Boolean + 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 canEvaluateClassReference(reference: IrDeclarationReference): Boolean = false - fun mustCheckBodyOf(function: IrFunction): Boolean { - if (function.property != null) return true - return (mustCheckBody || function.isLocal) && !function.isContract() && !function.isMarkedAsEvaluateIntrinsic() + open fun canEvaluateBlock(block: IrBlock): Boolean = false + open fun canEvaluateComposite(composite: IrComposite): Boolean { + return composite.origin == IrStatementOrigin.DESTRUCTURING_DECLARATION || composite.origin == null } - protected val compileTimeTypeAliases = setOf( - "java.lang.StringBuilder", "java.lang.IllegalArgumentException", "java.util.NoSuchElementException" - ) + open fun canEvaluateExpression(expression: IrExpression): Boolean = false + + open fun mustCheckBodyOf(function: IrFunction): Boolean = false - fun IrDeclaration.isMarkedAsCompileTime() = isMarkedWith(compileTimeAnnotation) protected fun IrDeclaration.isMarkedAsIntrinsicConstEvaluation() = isMarkedWith(intrinsicConstEvaluationAnnotation) - private fun IrDeclaration.isContract() = isMarkedWith(contractsDslAnnotation) - private fun IrDeclaration.isMarkedAsEvaluateIntrinsic() = isMarkedWith(evaluateIntrinsicAnnotation) - protected fun IrDeclaration.isCompileTimeTypeAlias() = this.parentClassOrNull?.fqName in compileTimeTypeAliases protected fun IrDeclaration.isMarkedWith(annotation: FqName): Boolean { if (this is IrClass && this.isCompanion) return false diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt index 780238a4be1..4e8da08e141 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.interpreter.fqName import org.jetbrains.kotlin.ir.interpreter.isAccessToNotNullableObject import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.util.statements import org.jetbrains.kotlin.ir.visitors.IrElementVisitor @@ -26,14 +27,14 @@ class IrCompileTimeChecker( private var contextExpression: IrCall? = null private val visitedStack = mutableListOf().apply { if (containingDeclaration != null) add(containingDeclaration) } - private fun IrElement.asVisited(block: () -> Boolean): Boolean { + private inline fun IrElement.asVisited(crossinline block: () -> Boolean): Boolean { visitedStack += this val result = block() visitedStack.removeAt(visitedStack.lastIndex) return result } - private fun IrCall.saveContext(block: () -> R): R { + private inline fun IrCall.saveContext(crossinline block: () -> R): R { val oldContext = contextExpression contextExpression = this return block().apply { contextExpression = oldContext } @@ -46,24 +47,21 @@ class IrCompileTimeChecker( } private fun visitStatements(statements: List): Boolean { - when (mode) { - EvaluationMode.ONLY_BUILTINS, EvaluationMode.ONLY_INTRINSIC_CONST -> { - val statement = statements.singleOrNull() ?: return false - return statement.accept(this, null) - } - else -> return statements.all { it.accept(this, null) } - } + return statements.all { it.accept(this, null) } } private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean { - return when { - !visitValueArguments(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false - else -> expression.symbol.owner.visitBodyIfNeeded() - } + val constructor = expression.symbol.owner + + if (!mode.canEvaluateFunction(constructor, contextExpression)) return false + if (!visitValueArguments(expression, null)) return false + return constructor.visitBodyIfNeeded() && + constructor.parentAsClass.declarations.filterIsInstance().all { it.accept(this, null) } } private fun IrFunction.visitBodyIfNeeded(): Boolean { - return this.asVisited { !mode.mustCheckBodyOf(this) || (this.body?.accept(this@IrCompileTimeChecker, null) ?: true) } + if (!mode.mustCheckBodyOf(this)) return true + return this.asVisited { this.body?.accept(this@IrCompileTimeChecker, null) ?: true } } private fun IrCall.isGetterToConstVal(): Boolean { @@ -71,6 +69,8 @@ class IrCompileTimeChecker( } override fun visitCall(expression: IrCall, data: Nothing?): Boolean { + if (!mode.canEvaluateExpression(expression)) return false + val owner = expression.symbol.owner if (!mode.canEvaluateFunction(owner, expression)) return false @@ -114,19 +114,23 @@ class IrCompileTimeChecker( } override fun visitBlock(expression: IrBlock, data: Nothing?): Boolean { - if (mode == EvaluationMode.ONLY_INTRINSIC_CONST && expression.origin == IrStatementOrigin.WHEN) { - return expression.statements.all { it.accept(this, null) } - } + if (!mode.canEvaluateBlock(expression)) return false // `IrReturnableBlock` will be created from IrCall after inline. We should do basically the same check as for IrCall. if (expression is IrReturnableBlock) { - // TODO after JVM inline MR 8122 will be pushed check original IrCall. - TODO("Interpretation of `IrReturnableBlock` is not implemented") + val inlinedBlock = expression.statements.singleOrNull() as? IrInlinedFunctionBlock + if (inlinedBlock != null) return inlinedBlock.inlineCall.accept(this, data) } return visitStatements(expression.statements) } + override fun visitComposite(expression: IrComposite, data: Nothing?): Boolean { + if (!mode.canEvaluateComposite(expression)) return false + + return visitStatements(expression.statements) + } + override fun visitSyntheticBody(body: IrSyntheticBody, data: Nothing?): Boolean { return body.kind == IrSyntheticBodyKind.ENUM_VALUES || body.kind == IrSyntheticBodyKind.ENUM_VALUEOF } @@ -147,13 +151,6 @@ class IrCompileTimeChecker( return spread.expression.accept(this, data) } - override fun visitComposite(expression: IrComposite, data: Nothing?): Boolean { - if (expression.origin == IrStatementOrigin.DESTRUCTURING_DECLARATION || expression.origin == null) { - return visitStatements(expression.statements) - } - return false - } - override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean { return expression.arguments.all { arg -> when (arg) { @@ -162,7 +159,7 @@ class IrCompileTimeChecker( .filterIsInstance() .single { it.name.asString() == "toString" && it.valueParameters.isEmpty() && it.extensionReceiverParameter == null } - mode.canEvaluateFunction(toString, null) && toString.visitBodyIfNeeded() + mode.canEvaluateFunction(toString) && toString.visitBodyIfNeeded() } else -> arg.accept(this, data) @@ -177,6 +174,7 @@ class IrCompileTimeChecker( override fun visitGetEnumValue(expression: IrGetEnumValue, data: Nothing?): Boolean { if (!mode.canEvaluateEnumValue(expression, contextExpression)) 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 return expression.asVisited { @@ -197,7 +195,7 @@ class IrCompileTimeChecker( val property = owner.correspondingPropertySymbol?.owner val fqName = owner.fqName fun isJavaStaticWithPrimitiveOrString(): Boolean { - return owner.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && owner.isStatic && + return owner.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && owner.isStatic && owner.isFinal && (owner.type.isPrimitiveType() || owner.type.isStringClassType()) } return when { @@ -247,7 +245,6 @@ class IrCompileTimeChecker( val classProperties = irClass.declarations.filterIsInstance() val anonymousInitializer = irClass.declarations.filterIsInstance().filter { !it.isStatic } - return anonymousInitializer.all { init -> init.body.accept(this, data) } && classProperties.all { val propertyInitializer = it.backingField?.initializer?.expression if ((propertyInitializer as? IrGetValue)?.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER) return@all true @@ -256,7 +253,7 @@ class IrCompileTimeChecker( } override fun visitFunctionReference(expression: IrFunctionReference, data: Nothing?): Boolean { - if (!mode.canEvaluateReference(expression, contextExpression)) return false + if (!mode.canEvaluateCallableReference(expression, contextExpression)) return false val owner = expression.symbol.owner val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true @@ -269,12 +266,10 @@ class IrCompileTimeChecker( } override fun visitFunctionExpression(expression: IrFunctionExpression, data: Nothing?): Boolean { - if (mode == EvaluationMode.ONLY_BUILTINS || mode == EvaluationMode.ONLY_INTRINSIC_CONST) return false - val isLambda = expression.origin == IrStatementOrigin.LAMBDA || expression.origin == IrStatementOrigin.ANONYMOUS_FUNCTION - val isCompileTime = mode.canEvaluateFunction(expression.function) - return expression.function.asVisited { - if (isLambda || isCompileTime) expression.function.body?.accept(this, data) == true else false - } + if (!mode.canEvaluateFunctionExpression(expression)) return false + + val body = expression.function.body ?: return false + return expression.function.asVisited { body.accept(this, data) } } override fun visitTypeOperator(expression: IrTypeOperatorCall, data: Nothing?): Boolean { @@ -291,6 +286,8 @@ class IrCompileTimeChecker( } override fun visitWhen(expression: IrWhen, data: Nothing?): Boolean { + if (!mode.canEvaluateExpression(expression)) return false + return expression.branches.all { it.accept(this, data) } } @@ -311,7 +308,8 @@ class IrCompileTimeChecker( } override fun visitTry(aTry: IrTry, data: Nothing?): Boolean { - if (mode == EvaluationMode.ONLY_BUILTINS || mode == EvaluationMode.ONLY_INTRINSIC_CONST) return false + if (!mode.canEvaluateExpression(aTry)) return false + if (!aTry.tryResult.accept(this, data)) return false if (aTry.finallyExpression != null && aTry.finallyExpression?.accept(this, data) == false) return false return aTry.catches.all { it.result.accept(this, data) } @@ -327,11 +325,13 @@ class IrCompileTimeChecker( } override fun visitThrow(expression: IrThrow, data: Nothing?): Boolean { + if (!mode.canEvaluateExpression(expression)) return false + return expression.value.accept(this, data) } override fun visitPropertyReference(expression: IrPropertyReference, data: Nothing?): Boolean { - if (!mode.canEvaluateReference(expression, contextExpression)) return false + if (!mode.canEvaluateCallableReference(expression, contextExpression)) return false val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true @@ -341,11 +341,6 @@ class IrCompileTimeChecker( } override fun visitClassReference(expression: IrClassReference, data: Nothing?): Boolean { - return with(mode) { - when (this) { - EvaluationMode.FULL -> true - EvaluationMode.ONLY_BUILTINS, EvaluationMode.ONLY_INTRINSIC_CONST -> false - } - } + return mode.canEvaluateClassReference(expression) } } \ No newline at end of file