From 43fe8c493143d3c44894c98b2fcebe4b3538f732 Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Thu, 17 Dec 2020 18:19:01 +0500 Subject: [PATCH] [IR] Added printing more info in a error message Print all captured values for bound function references when they're prohibited --- .../lower/SpecialBackendChecksTraversal.kt | 88 +++++++++++++++---- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt index e17c5790fd4..855cf466a0c 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt @@ -5,16 +5,20 @@ package org.jetbrains.kotlin.backend.konan.lower -import org.jetbrains.kotlin.backend.common.* +import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.ir.Symbols import org.jetbrains.kotlin.backend.common.ir.allParameters import org.jetbrains.kotlin.backend.common.lower.Closure import org.jetbrains.kotlin.backend.common.lower.ClosureAnnotator +import org.jetbrains.kotlin.backend.common.peek +import org.jetbrains.kotlin.backend.common.pop +import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.cgen.* import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions -import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.ir.companionObject +import org.jetbrains.kotlin.backend.konan.ir.getSuperClassNotAny +import org.jetbrains.kotlin.backend.konan.ir.isReal import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -22,7 +26,6 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.isFinalClass import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol @@ -34,6 +37,8 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.utils.fileUtils.descendantRelativeTo +import java.io.File internal class SpecialBackendChecksTraversal(val context: Context) : FileLoweringPass { override fun lower(irFile: IrFile) = irFile.acceptChildrenVoid(BackendChecker(context, irFile)) @@ -57,12 +62,12 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme private val functionAnnotators = mutableMapOf>() private val functionClosures = mutableMapOf() - private fun capturesAnything(function: IrFunction): Boolean { - if (function.visibility != DescriptorVisibilities.LOCAL) return false + private fun captures(function: IrFunction): List { + if (function.visibility != DescriptorVisibilities.LOCAL) return emptyList() val closure = functionClosures.getOrPut(function) { functionAnnotators[function]!!.value.getFunctionClosure(function) } - return closure.capturedValues.isNotEmpty() + return closure.capturedValues.map { it.owner } } override fun visitElement(element: IrElement) { @@ -332,6 +337,22 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme } } + private val cwd = File(".").absoluteFile + + private fun reportBoundFunctionReferenceError(expression: IrExpression, callee: IrFunction, captures: List): Nothing { + val formattedCaptures = if (captures.isEmpty()) + "" + else ", but captures at:\n ${captures.joinToString("\n ") { + val location = it.getCompilerMessageLocation(irFile)!! + val relativePath = File(location.path).descendantRelativeTo(cwd).path + val capturedValueName = if (it is IrGetValue) ": ${it.symbol.owner.name.asString()}" else "" + "$relativePath:${location.line}:${location.column}$capturedValueName" + }}" + + reportError(expression, + "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda$formattedCaptures") + } + override fun visitCall(expression: IrCall) { expression.acceptChildrenVoid(this) @@ -377,10 +398,10 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme when (val intrinsicType = tryGetIntrinsicType(expression)) { IntrinsicType.INTEROP_STATIC_C_FUNCTION -> { - val target = getUnboundReferencedFunction(expression.getValueArgument(0)!!) + val (target, captures) = getUnboundReferencedFunction(expression.getValueArgument(0)!!) if (target == null || target.symbol !is IrSimpleFunctionSymbol) - reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda") + reportBoundFunctionReferenceError(expression, callee, captures) val signatureTypes = target.allParameters.map { it.type } + target.returnType @@ -439,13 +460,16 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme reportError(expression, "unable to convert ${receiverType.toKotlinType()} to ${typeOperand.toKotlinType()}") } IntrinsicType.WORKER_EXECUTE -> { - getUnboundReferencedFunction(expression.getValueArgument(2)!!) - ?: reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda") + val (function, captures) = getUnboundReferencedFunction(expression.getValueArgument(2)!!) + if (function == null) + reportBoundFunctionReferenceError(expression, callee, captures) } else -> when { - callee.symbol == symbols.createCleaner -> - getUnboundReferencedFunction(expression.getValueArgument(1)!!) - ?: reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda") + callee.symbol == symbols.createCleaner -> { + val (function, captures) = getUnboundReferencedFunction(expression.getValueArgument(1)!!) + if (function == null) + reportBoundFunctionReferenceError(expression, callee, captures) + } callee.symbol == symbols.immutableBlobOf -> { val args = expression.getValueArgument(0) ?: reportError(expression, "expected at least one element") @@ -492,10 +516,42 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme } } + private data class ReferencedFunctionWithCapture(val function: IrFunction?, val captures: List) + + private fun searchForReferences(function: IrFunction, targetValues: Set): List { + if (targetValues.isEmpty()) return emptyList() + + val result = mutableListOf() + function.acceptChildrenVoid(object: IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitGetValue(expression: IrGetValue) { + if (expression.symbol.owner in targetValues) + result += expression + } + }) + return result + } + private fun getUnboundReferencedFunction(expression: IrExpression) = when (expression) { - is IrFunctionReference -> expression.symbol.owner.takeIf { expression.getArguments().isEmpty() && !capturesAnything(it) } - is IrFunctionExpression -> expression.function.takeIf { !capturesAnything(it) } - else -> null + is IrFunctionReference -> { + val arguments = expression.getArguments() + val capturedVariables = captures(expression.symbol.owner) + ReferencedFunctionWithCapture( + expression.symbol.owner.takeIf { arguments.isEmpty() && capturedVariables.isEmpty() }, + arguments.map { it.second } + searchForReferences(expression.symbol.owner, capturedVariables.toSet()) + ) + } + is IrFunctionExpression -> { + val capturedVariables = captures(expression.function) + ReferencedFunctionWithCapture( + expression.function.takeIf { capturedVariables.isEmpty() }, + searchForReferences(expression.function, capturedVariables.toSet()) + ) + } + else -> ReferencedFunctionWithCapture(null, emptyList()) } private fun IrCall.getSingleTypeArgument(): IrType {