[IR] Added printing more info in a error message
Print all captured values for bound function references when they're prohibited
This commit is contained in:
committed by
Stanislav Erokhin
parent
2df2c2f055
commit
43fe8c4931
+72
-16
@@ -5,16 +5,20 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.backend.konan.lower
|
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.Symbols
|
||||||
import org.jetbrains.kotlin.backend.common.ir.allParameters
|
import org.jetbrains.kotlin.backend.common.ir.allParameters
|
||||||
import org.jetbrains.kotlin.backend.common.lower.Closure
|
import org.jetbrains.kotlin.backend.common.lower.Closure
|
||||||
import org.jetbrains.kotlin.backend.common.lower.ClosureAnnotator
|
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.*
|
||||||
import org.jetbrains.kotlin.backend.konan.cgen.*
|
import org.jetbrains.kotlin.backend.konan.cgen.*
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
|
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.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.IntrinsicType
|
||||||
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
|
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
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.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.descriptors.isFinalClass
|
import org.jetbrains.kotlin.descriptors.isFinalClass
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
|
||||||
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.symbols.IrSimpleFunctionSymbol
|
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.name.FqName
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
import org.jetbrains.kotlin.types.Variance
|
import org.jetbrains.kotlin.types.Variance
|
||||||
|
import org.jetbrains.kotlin.utils.fileUtils.descendantRelativeTo
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
internal class SpecialBackendChecksTraversal(val context: Context) : FileLoweringPass {
|
internal class SpecialBackendChecksTraversal(val context: Context) : FileLoweringPass {
|
||||||
override fun lower(irFile: IrFile) = irFile.acceptChildrenVoid(BackendChecker(context, irFile))
|
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<IrFunction, Lazy<ClosureAnnotator>>()
|
private val functionAnnotators = mutableMapOf<IrFunction, Lazy<ClosureAnnotator>>()
|
||||||
private val functionClosures = mutableMapOf<IrFunction, Closure>()
|
private val functionClosures = mutableMapOf<IrFunction, Closure>()
|
||||||
|
|
||||||
private fun capturesAnything(function: IrFunction): Boolean {
|
private fun captures(function: IrFunction): List<IrValueDeclaration> {
|
||||||
if (function.visibility != DescriptorVisibilities.LOCAL) return false
|
if (function.visibility != DescriptorVisibilities.LOCAL) return emptyList()
|
||||||
val closure = functionClosures.getOrPut(function) {
|
val closure = functionClosures.getOrPut(function) {
|
||||||
functionAnnotators[function]!!.value.getFunctionClosure(function)
|
functionAnnotators[function]!!.value.getFunctionClosure(function)
|
||||||
}
|
}
|
||||||
return closure.capturedValues.isNotEmpty()
|
return closure.capturedValues.map { it.owner }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitElement(element: IrElement) {
|
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<IrExpression>): 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) {
|
override fun visitCall(expression: IrCall) {
|
||||||
expression.acceptChildrenVoid(this)
|
expression.acceptChildrenVoid(this)
|
||||||
|
|
||||||
@@ -377,10 +398,10 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme
|
|||||||
|
|
||||||
when (val intrinsicType = tryGetIntrinsicType(expression)) {
|
when (val intrinsicType = tryGetIntrinsicType(expression)) {
|
||||||
IntrinsicType.INTEROP_STATIC_C_FUNCTION -> {
|
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)
|
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
|
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()}")
|
reportError(expression, "unable to convert ${receiverType.toKotlinType()} to ${typeOperand.toKotlinType()}")
|
||||||
}
|
}
|
||||||
IntrinsicType.WORKER_EXECUTE -> {
|
IntrinsicType.WORKER_EXECUTE -> {
|
||||||
getUnboundReferencedFunction(expression.getValueArgument(2)!!)
|
val (function, captures) = getUnboundReferencedFunction(expression.getValueArgument(2)!!)
|
||||||
?: reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda")
|
if (function == null)
|
||||||
|
reportBoundFunctionReferenceError(expression, callee, captures)
|
||||||
}
|
}
|
||||||
else -> when {
|
else -> when {
|
||||||
callee.symbol == symbols.createCleaner ->
|
callee.symbol == symbols.createCleaner -> {
|
||||||
getUnboundReferencedFunction(expression.getValueArgument(1)!!)
|
val (function, captures) = getUnboundReferencedFunction(expression.getValueArgument(1)!!)
|
||||||
?: reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda")
|
if (function == null)
|
||||||
|
reportBoundFunctionReferenceError(expression, callee, captures)
|
||||||
|
}
|
||||||
callee.symbol == symbols.immutableBlobOf -> {
|
callee.symbol == symbols.immutableBlobOf -> {
|
||||||
val args = expression.getValueArgument(0)
|
val args = expression.getValueArgument(0)
|
||||||
?: reportError(expression, "expected at least one element")
|
?: 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<IrExpression>)
|
||||||
|
|
||||||
|
private fun searchForReferences(function: IrFunction, targetValues: Set<IrValueDeclaration>): List<IrExpression> {
|
||||||
|
if (targetValues.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val result = mutableListOf<IrExpression>()
|
||||||
|
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) {
|
private fun getUnboundReferencedFunction(expression: IrExpression) = when (expression) {
|
||||||
is IrFunctionReference -> expression.symbol.owner.takeIf { expression.getArguments().isEmpty() && !capturesAnything(it) }
|
is IrFunctionReference -> {
|
||||||
is IrFunctionExpression -> expression.function.takeIf { !capturesAnything(it) }
|
val arguments = expression.getArguments()
|
||||||
else -> null
|
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 {
|
private fun IrCall.getSingleTypeArgument(): IrType {
|
||||||
|
|||||||
Reference in New Issue
Block a user