From 1fd8ef801e0938d6f0a40a2e411cf6d686520eb6 Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Thu, 27 Apr 2023 17:41:33 +0200 Subject: [PATCH] [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. --- ...LightTreeBlackBoxCodegenTestGenerated.java | 6 +++ .../FirPsiBlackBoxCodegenTestGenerated.java | 6 +++ .../ir/interpreter/checker/EvaluationMode.kt | 43 ++++++------------- .../checker/IrCompileTimeChecker.kt | 39 +++++++---------- .../checker/IrCompileTimeNameChecker.kt | 38 ++++++++++++++++ .../transformer/IrConstTransformer.kt | 4 +- .../kCallableNameWithSideEffect.kt | 36 ++++++++++++++++ .../IrBlackBoxCodegenTestGenerated.java | 6 +++ ...kBoxCodegenWithIrInlinerTestGenerated.java | 6 +++ 9 files changed, 130 insertions(+), 54 deletions(-) create mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeNameChecker.kt create mode 100644 compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java index 10cf32e55b7..b3f5b867d4a 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java @@ -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 { diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java index 12e95721574..067fc246f97 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java @@ -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 { 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 4db265f4bdc..61e559f5f3c 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 @@ -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) 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 4e8da08e141..2a6e70bcd96 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 @@ -24,7 +24,6 @@ class IrCompileTimeChecker( private val mode: EvaluationMode, private val interpreterConfiguration: IrInterpreterConfiguration, ) : IrElementVisitor { - private var contextExpression: IrCall? = null private val visitedStack = mutableListOf().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 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 { @@ -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().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 } diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeNameChecker.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeNameChecker.kt new file mode 100644 index 00000000000..fd231901c75 --- /dev/null +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeNameChecker.kt @@ -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 { + 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) + } +} diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt index 79cc73a2546..08c6dfc1813 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt @@ -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 diff --git a/compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt b/compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt new file mode 100644 index 00000000000..9bdd01e2621 --- /dev/null +++ b/compiler/testData/codegen/box/involvesIrInterpreter/intrinsicConst/kCallableNameWithSideEffect.kt @@ -0,0 +1,36 @@ +// !LANGUAGE: +IntrinsicConstEvaluation +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +fun T.id() = this + +class A { + val a = "" + fun b() = "" + + init { + println("A init") + } + + fun test() { + val a = A::a.name + val b = A::b.name + + val c = ::A.")!>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" +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 5f75c4cb5a8..acb878342e0 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -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 { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java index e3eeba41c2d..b85e93c09f6 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java @@ -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 {