[PowerAssert] Reformat files
This commit is contained in:
+2
-2
@@ -20,6 +20,6 @@
|
||||
package org.jetbrains.kotlin.powerassert.gradle
|
||||
|
||||
open class PowerAssertGradleExtension {
|
||||
var functions: List<String> = listOf("kotlin.assert")
|
||||
var excludedSourceSets: List<String> = listOf()
|
||||
var functions: List<String> = listOf("kotlin.assert")
|
||||
var excludedSourceSets: List<String> = listOf()
|
||||
}
|
||||
|
||||
+28
-28
@@ -27,33 +27,33 @@ import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
|
||||
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
|
||||
|
||||
class PowerAssertGradlePlugin : KotlinCompilerPluginSupportPlugin {
|
||||
override fun apply(target: Project): Unit = with(target) {
|
||||
extensions.create("kotlinPowerAssert", PowerAssertGradleExtension::class.java)
|
||||
}
|
||||
|
||||
override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
|
||||
val project = kotlinCompilation.target.project
|
||||
val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java)
|
||||
return extension.excludedSourceSets.none { it == kotlinCompilation.defaultSourceSet.name }
|
||||
}
|
||||
|
||||
override fun getCompilerPluginId(): String = "com.bnorm.kotlin-power-assert"
|
||||
|
||||
override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
|
||||
groupId = BuildConfig.PLUGIN_GROUP_ID,
|
||||
artifactId = BuildConfig.PLUGIN_ARTIFACT_ID,
|
||||
version = BuildConfig.PLUGIN_VERSION,
|
||||
)
|
||||
|
||||
override fun applyToCompilation(
|
||||
kotlinCompilation: KotlinCompilation<*>,
|
||||
): Provider<List<SubpluginOption>> {
|
||||
val project = kotlinCompilation.target.project
|
||||
val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java)
|
||||
return project.provider {
|
||||
extension.functions.map {
|
||||
SubpluginOption(key = "function", value = it)
|
||||
}
|
||||
override fun apply(target: Project): Unit = with(target) {
|
||||
extensions.create("kotlinPowerAssert", PowerAssertGradleExtension::class.java)
|
||||
}
|
||||
|
||||
override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
|
||||
val project = kotlinCompilation.target.project
|
||||
val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java)
|
||||
return extension.excludedSourceSets.none { it == kotlinCompilation.defaultSourceSet.name }
|
||||
}
|
||||
|
||||
override fun getCompilerPluginId(): String = "com.bnorm.kotlin-power-assert"
|
||||
|
||||
override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
|
||||
groupId = BuildConfig.PLUGIN_GROUP_ID,
|
||||
artifactId = BuildConfig.PLUGIN_ARTIFACT_ID,
|
||||
version = BuildConfig.PLUGIN_VERSION,
|
||||
)
|
||||
|
||||
override fun applyToCompilation(
|
||||
kotlinCompilation: KotlinCompilation<*>,
|
||||
): Provider<List<SubpluginOption>> {
|
||||
val project = kotlinCompilation.target.project
|
||||
val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java)
|
||||
return project.provider {
|
||||
extension.functions.map {
|
||||
SubpluginOption(key = "function", value = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-26
@@ -21,12 +21,8 @@ package org.jetbrains.kotlin.powerassert
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
import org.jetbrains.kotlin.ir.builders.irBlockBody
|
||||
import org.jetbrains.kotlin.ir.builders.irString
|
||||
import org.jetbrains.kotlin.ir.builders.parent
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
@@ -35,29 +31,29 @@ import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
fun IrBuilderWithScope.irString(builderAction: StringBuilder.() -> Unit) =
|
||||
irString(buildString { builderAction() })
|
||||
irString(buildString { builderAction() })
|
||||
|
||||
fun IrBuilderWithScope.irLambda(
|
||||
returnType: IrType,
|
||||
lambdaType: IrType,
|
||||
startOffset: Int = this.startOffset,
|
||||
endOffset: Int = this.endOffset,
|
||||
block: IrBlockBodyBuilder.() -> Unit,
|
||||
returnType: IrType,
|
||||
lambdaType: IrType,
|
||||
startOffset: Int = this.startOffset,
|
||||
endOffset: Int = this.endOffset,
|
||||
block: IrBlockBodyBuilder.() -> Unit,
|
||||
): IrFunctionExpression {
|
||||
val scope = this
|
||||
val lambda = context.irFactory.buildFun {
|
||||
this.startOffset = startOffset
|
||||
this.endOffset = endOffset
|
||||
name = Name.special("<anonymous>")
|
||||
this.returnType = returnType
|
||||
visibility = DescriptorVisibilities.LOCAL
|
||||
origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
|
||||
}.apply {
|
||||
val bodyBuilder = DeclarationIrBuilder(context, symbol, startOffset, endOffset)
|
||||
body = bodyBuilder.irBlockBody {
|
||||
block()
|
||||
val scope = this
|
||||
val lambda = context.irFactory.buildFun {
|
||||
this.startOffset = startOffset
|
||||
this.endOffset = endOffset
|
||||
name = Name.special("<anonymous>")
|
||||
this.returnType = returnType
|
||||
visibility = DescriptorVisibilities.LOCAL
|
||||
origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
|
||||
}.apply {
|
||||
val bodyBuilder = DeclarationIrBuilder(context, symbol, startOffset, endOffset)
|
||||
body = bodyBuilder.irBlockBody {
|
||||
block()
|
||||
}
|
||||
parent = scope.parent
|
||||
}
|
||||
parent = scope.parent
|
||||
}
|
||||
return IrFunctionExpressionImpl(startOffset, endOffset, lambdaType, lambda, IrStatementOrigin.LAMBDA)
|
||||
return IrFunctionExpressionImpl(startOffset, endOffset, lambdaType, lambda, IrStatementOrigin.LAMBDA)
|
||||
}
|
||||
|
||||
+211
-232
@@ -19,17 +19,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.powerassert
|
||||
|
||||
import org.jetbrains.kotlin.powerassert.delegate.FunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.delegate.LambdaFunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.delegate.SamConversionLambdaFunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.delegate.SimpleFunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.diagram.IrTemporaryVariable
|
||||
import org.jetbrains.kotlin.powerassert.diagram.Node
|
||||
import org.jetbrains.kotlin.powerassert.diagram.SourceFile
|
||||
import org.jetbrains.kotlin.powerassert.diagram.buildDiagramNesting
|
||||
import org.jetbrains.kotlin.powerassert.diagram.buildDiagramNestingNullable
|
||||
import org.jetbrains.kotlin.powerassert.diagram.buildTree
|
||||
import org.jetbrains.kotlin.powerassert.diagram.irDiagramString
|
||||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||
@@ -44,24 +33,9 @@ import org.jetbrains.kotlin.ir.builders.parent
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetValue
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeArgument
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeProjection
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.types.classifierOrNull
|
||||
import org.jetbrains.kotlin.ir.types.isBoolean
|
||||
import org.jetbrains.kotlin.ir.types.isSubtypeOf
|
||||
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.classId
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
@@ -71,240 +45,245 @@ import org.jetbrains.kotlin.ir.util.kotlinFqName
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.powerassert.delegate.FunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.delegate.LambdaFunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.delegate.SamConversionLambdaFunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.delegate.SimpleFunctionDelegate
|
||||
import org.jetbrains.kotlin.powerassert.diagram.*
|
||||
|
||||
class PowerAssertCallTransformer(
|
||||
private val sourceFile: SourceFile,
|
||||
private val context: IrPluginContext,
|
||||
private val messageCollector: MessageCollector,
|
||||
private val functions: Set<FqName>,
|
||||
private val sourceFile: SourceFile,
|
||||
private val context: IrPluginContext,
|
||||
private val messageCollector: MessageCollector,
|
||||
private val functions: Set<FqName>,
|
||||
) : IrElementTransformerVoidWithContext() {
|
||||
private val irTypeSystemContext = IrTypeSystemContextImpl(context.irBuiltIns)
|
||||
private val irTypeSystemContext = IrTypeSystemContextImpl(context.irBuiltIns)
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val function = expression.symbol.owner
|
||||
val fqName = function.kotlinFqName
|
||||
if (function.valueParameters.isEmpty() || functions.none { fqName == it }) {
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val function = expression.symbol.owner
|
||||
val fqName = function.kotlinFqName
|
||||
if (function.valueParameters.isEmpty() || functions.none { fqName == it }) {
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
// Find a valid delegate function or do not translate
|
||||
// TODO better way to determine which delegate to actually use
|
||||
val delegates = findDelegates(function)
|
||||
val delegate = delegates.maxByOrNull { it.function.valueParameters.size }
|
||||
if (delegate == null) {
|
||||
val valueTypesTruncated = function.valueParameters.subList(0, function.valueParameters.size - 1)
|
||||
.joinToString("") { it.type.asString() + ", " }
|
||||
val valueTypesAll = function.valueParameters.joinToString("") { it.type.asString() + ", " }
|
||||
messageCollector.warn(
|
||||
expression = expression,
|
||||
message = """
|
||||
// Find a valid delegate function or do not translate
|
||||
// TODO better way to determine which delegate to actually use
|
||||
val delegates = findDelegates(function)
|
||||
val delegate = delegates.maxByOrNull { it.function.valueParameters.size }
|
||||
if (delegate == null) {
|
||||
val valueTypesTruncated = function.valueParameters.subList(0, function.valueParameters.size - 1)
|
||||
.joinToString("") { it.type.asString() + ", " }
|
||||
val valueTypesAll = function.valueParameters.joinToString("") { it.type.asString() + ", " }
|
||||
messageCollector.warn(
|
||||
expression = expression,
|
||||
message = """
|
||||
|Unable to find overload of function $fqName for power-assert transformation callable as:
|
||||
| - $fqName(${valueTypesTruncated}String)
|
||||
| - $fqName($valueTypesTruncated() -> String)
|
||||
| - $fqName(${valueTypesAll}String)
|
||||
| - $fqName($valueTypesAll() -> String)
|
||||
""".trimMargin(),
|
||||
)
|
||||
return super.visitCall(expression)
|
||||
)
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
val dispatchRoot =
|
||||
if (expression.symbol.owner.isInfix) expression.dispatchReceiver?.let { buildTree(it) } else null
|
||||
val extensionRoot =
|
||||
if (expression.symbol.owner.isInfix) expression.extensionReceiver?.let { buildTree(it) } else null
|
||||
val messageArgument: IrExpression?
|
||||
val roots: List<Node?>
|
||||
if (delegate.function.valueParameters.size == function.valueParameters.size) {
|
||||
messageArgument = expression.getValueArgument(expression.valueArgumentsCount - 1)
|
||||
roots = (0 until expression.valueArgumentsCount - 1)
|
||||
.map { index -> expression.getValueArgument(index) }
|
||||
.map { arg -> arg?.let { buildTree(it) } }
|
||||
} else {
|
||||
messageArgument = null
|
||||
roots = (0 until expression.valueArgumentsCount)
|
||||
.map { index -> expression.getValueArgument(index) }
|
||||
.map { arg -> arg?.let { buildTree(it) } }
|
||||
}
|
||||
|
||||
// If all roots are null, there are no transformable parameters
|
||||
if (dispatchRoot == null && extensionRoot == null && roots.all { it == null }) {
|
||||
messageCollector.info(expression, "Expression is constant and will not be power-assert transformed")
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
val symbol = currentScope!!.scope.scopeOwnerSymbol
|
||||
val builder = DeclarationIrBuilder(context, symbol, expression.startOffset, expression.endOffset)
|
||||
return builder.diagram(
|
||||
call = expression,
|
||||
delegate = delegate,
|
||||
messageArgument = messageArgument,
|
||||
roots = roots,
|
||||
dispatchRoot = dispatchRoot,
|
||||
extensionRoot = extensionRoot,
|
||||
)
|
||||
}
|
||||
|
||||
val dispatchRoot =
|
||||
if (expression.symbol.owner.isInfix) expression.dispatchReceiver?.let { buildTree(it) } else null
|
||||
val extensionRoot =
|
||||
if (expression.symbol.owner.isInfix) expression.extensionReceiver?.let { buildTree(it) } else null
|
||||
val messageArgument: IrExpression?
|
||||
val roots: List<Node?>
|
||||
if (delegate.function.valueParameters.size == function.valueParameters.size) {
|
||||
messageArgument = expression.getValueArgument(expression.valueArgumentsCount - 1)
|
||||
roots = (0 until expression.valueArgumentsCount - 1)
|
||||
.map { index -> expression.getValueArgument(index) }
|
||||
.map { arg -> arg?.let { buildTree(it) } }
|
||||
} else {
|
||||
messageArgument = null
|
||||
roots = (0 until expression.valueArgumentsCount)
|
||||
.map { index -> expression.getValueArgument(index) }
|
||||
.map { arg -> arg?.let { buildTree(it) } }
|
||||
}
|
||||
|
||||
// If all roots are null, there are no transformable parameters
|
||||
if (dispatchRoot == null && extensionRoot == null && roots.all { it == null }) {
|
||||
messageCollector.info(expression, "Expression is constant and will not be power-assert transformed")
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
val symbol = currentScope!!.scope.scopeOwnerSymbol
|
||||
val builder = DeclarationIrBuilder(context, symbol, expression.startOffset, expression.endOffset)
|
||||
return builder.diagram(
|
||||
call = expression,
|
||||
delegate = delegate,
|
||||
messageArgument = messageArgument,
|
||||
roots = roots,
|
||||
dispatchRoot = dispatchRoot,
|
||||
extensionRoot = extensionRoot,
|
||||
)
|
||||
}
|
||||
|
||||
private fun DeclarationIrBuilder.diagram(
|
||||
call: IrCall,
|
||||
delegate: FunctionDelegate,
|
||||
messageArgument: IrExpression?,
|
||||
roots: List<Node?>,
|
||||
dispatchRoot: Node? = null,
|
||||
extensionRoot: Node? = null,
|
||||
): IrExpression {
|
||||
fun recursive(
|
||||
index: Int,
|
||||
dispatch: IrExpression?,
|
||||
extension: IrExpression?,
|
||||
arguments: List<IrExpression?>,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
private fun DeclarationIrBuilder.diagram(
|
||||
call: IrCall,
|
||||
delegate: FunctionDelegate,
|
||||
messageArgument: IrExpression?,
|
||||
roots: List<Node?>,
|
||||
dispatchRoot: Node? = null,
|
||||
extensionRoot: Node? = null,
|
||||
): IrExpression {
|
||||
if (index >= roots.size) {
|
||||
val prefix = buildMessagePrefix(messageArgument, delegate.messageParameter, roots, call)
|
||||
?.deepCopyWithSymbols(parent)
|
||||
val diagram = irDiagramString(sourceFile, prefix, call, variables)
|
||||
return delegate.buildCall(this, call, dispatch, extension, arguments, diagram)
|
||||
} else {
|
||||
val root = roots[index]
|
||||
if (root == null) {
|
||||
val newArguments = arguments + call.getValueArgument(index)
|
||||
return recursive(index + 1, dispatch, extension, newArguments, variables)
|
||||
} else {
|
||||
return buildDiagramNesting(root, variables) { argument, newVariables ->
|
||||
val newArguments = arguments + argument
|
||||
recursive(index + 1, dispatch, extension, newArguments, newVariables)
|
||||
}
|
||||
fun recursive(
|
||||
index: Int,
|
||||
dispatch: IrExpression?,
|
||||
extension: IrExpression?,
|
||||
arguments: List<IrExpression?>,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
): IrExpression {
|
||||
if (index >= roots.size) {
|
||||
val prefix = buildMessagePrefix(messageArgument, delegate.messageParameter, roots, call)
|
||||
?.deepCopyWithSymbols(parent)
|
||||
val diagram = irDiagramString(sourceFile, prefix, call, variables)
|
||||
return delegate.buildCall(this, call, dispatch, extension, arguments, diagram)
|
||||
} else {
|
||||
val root = roots[index]
|
||||
if (root == null) {
|
||||
val newArguments = arguments + call.getValueArgument(index)
|
||||
return recursive(index + 1, dispatch, extension, newArguments, variables)
|
||||
} else {
|
||||
return buildDiagramNesting(root, variables) { argument, newVariables ->
|
||||
val newArguments = arguments + argument
|
||||
recursive(index + 1, dispatch, extension, newArguments, newVariables)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buildDiagramNestingNullable(dispatchRoot) { dispatch, dispatchNewVariables ->
|
||||
buildDiagramNestingNullable(extensionRoot, dispatchNewVariables) { extension, extensionNewVariables ->
|
||||
recursive(0, dispatch, extension, emptyList(), extensionNewVariables)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DeclarationIrBuilder.buildMessagePrefix(
|
||||
messageArgument: IrExpression?,
|
||||
messageParameter: IrValueParameter,
|
||||
roots: List<Node?>,
|
||||
original: IrCall,
|
||||
): IrExpression? {
|
||||
return when {
|
||||
messageArgument is IrConst<*> -> messageArgument
|
||||
messageArgument is IrStringConcatenation -> messageArgument
|
||||
messageArgument is IrGetValue -> {
|
||||
if (messageArgument.type.isAssignableTo(context.irBuiltIns.stringType)) {
|
||||
return messageArgument
|
||||
} else {
|
||||
val invoke = messageParameter.type.classOrNull!!.functions
|
||||
.filter { !it.owner.isFakeOverride } // TODO best way to find single access method?
|
||||
.single()
|
||||
irCall(invoke).apply { dispatchReceiver = messageArgument }
|
||||
return buildDiagramNestingNullable(dispatchRoot) { dispatch, dispatchNewVariables ->
|
||||
buildDiagramNestingNullable(extensionRoot, dispatchNewVariables) { extension, extensionNewVariables ->
|
||||
recursive(0, dispatch, extension, emptyList(), extensionNewVariables)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Kotlin Lambda or SAMs conversion lambda
|
||||
messageArgument is IrFunctionExpression || messageArgument is IrTypeOperatorCall -> {
|
||||
val invoke = messageParameter.type.classOrNull!!.functions
|
||||
.filter { !it.owner.isFakeOverride } // TODO best way to find single access method?
|
||||
.single()
|
||||
irCall(invoke).apply { dispatchReceiver = messageArgument }
|
||||
}
|
||||
// TODO what should the default message be?
|
||||
roots.size == 1 && original.getValueArgument(0)!!.type.isBoolean() -> irString("Assertion failed")
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun findDelegates(function: IrFunction): List<FunctionDelegate> {
|
||||
val values = function.valueParameters
|
||||
if (values.isEmpty()) return emptyList()
|
||||
|
||||
// Java static functions require searching by class
|
||||
val parentClassFunctions = (
|
||||
function.parentClassId
|
||||
?.let { context.referenceClass(it) }
|
||||
?.functions ?: emptySequence()
|
||||
)
|
||||
.filter { it.owner.kotlinFqName == function.kotlinFqName }
|
||||
.toList()
|
||||
val possible = (context.referenceFunctions(function.callableId) + parentClassFunctions)
|
||||
.distinct()
|
||||
|
||||
return possible.mapNotNull { overload ->
|
||||
// Dispatch receivers must always match exactly
|
||||
if (function.dispatchReceiverParameter?.type != overload.owner.dispatchReceiverParameter?.type) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
// Extension receiver may only be assignable
|
||||
if (!function.extensionReceiverParameter?.type.isAssignableTo(overload.owner.extensionReceiverParameter?.type)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val parameters = overload.owner.valueParameters
|
||||
if (parameters.size !in values.size..values.size + 1) return@mapNotNull null
|
||||
if (!parameters.zip(values).all { (param, value) -> value.type.isAssignableTo(param.type) }) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val messageParameter = parameters.last()
|
||||
return@mapNotNull when {
|
||||
isStringSupertype(messageParameter.type) -> SimpleFunctionDelegate(overload, messageParameter)
|
||||
isStringFunction(messageParameter.type) -> LambdaFunctionDelegate(overload, messageParameter)
|
||||
isStringJavaSupplierFunction(messageParameter.type) ->
|
||||
SamConversionLambdaFunctionDelegate(overload, messageParameter)
|
||||
else -> null
|
||||
}
|
||||
private fun DeclarationIrBuilder.buildMessagePrefix(
|
||||
messageArgument: IrExpression?,
|
||||
messageParameter: IrValueParameter,
|
||||
roots: List<Node?>,
|
||||
original: IrCall,
|
||||
): IrExpression? {
|
||||
return when {
|
||||
messageArgument is IrConst<*> -> messageArgument
|
||||
messageArgument is IrStringConcatenation -> messageArgument
|
||||
messageArgument is IrGetValue -> {
|
||||
if (messageArgument.type.isAssignableTo(context.irBuiltIns.stringType)) {
|
||||
return messageArgument
|
||||
} else {
|
||||
val invoke = messageParameter.type.classOrNull!!.functions
|
||||
.filter { !it.owner.isFakeOverride } // TODO best way to find single access method?
|
||||
.single()
|
||||
irCall(invoke).apply { dispatchReceiver = messageArgument }
|
||||
}
|
||||
}
|
||||
// Kotlin Lambda or SAMs conversion lambda
|
||||
messageArgument is IrFunctionExpression || messageArgument is IrTypeOperatorCall -> {
|
||||
val invoke = messageParameter.type.classOrNull!!.functions
|
||||
.filter { !it.owner.isFakeOverride } // TODO best way to find single access method?
|
||||
.single()
|
||||
irCall(invoke).apply { dispatchReceiver = messageArgument }
|
||||
}
|
||||
// TODO what should the default message be?
|
||||
roots.size == 1 && original.getValueArgument(0)!!.type.isBoolean() -> irString("Assertion failed")
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isStringFunction(type: IrType): Boolean =
|
||||
type.isFunctionOrKFunction() && type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
private fun findDelegates(function: IrFunction): List<FunctionDelegate> {
|
||||
val values = function.valueParameters
|
||||
if (values.isEmpty()) return emptyList()
|
||||
|
||||
private fun isStringJavaSupplierFunction(type: IrType): Boolean {
|
||||
val javaSupplier = context.referenceClass(ClassId.topLevel(FqName("java.util.function.Supplier")))
|
||||
return javaSupplier != null && type.isSubtypeOfClass(javaSupplier) &&
|
||||
type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
}
|
||||
// Java static functions require searching by class
|
||||
val parentClassFunctions = (
|
||||
function.parentClassId
|
||||
?.let { context.referenceClass(it) }
|
||||
?.functions ?: emptySequence()
|
||||
)
|
||||
.filter { it.owner.kotlinFqName == function.kotlinFqName }
|
||||
.toList()
|
||||
val possible = (context.referenceFunctions(function.callableId) + parentClassFunctions)
|
||||
.distinct()
|
||||
|
||||
private fun isStringSupertype(argument: IrTypeArgument): Boolean =
|
||||
argument is IrTypeProjection && isStringSupertype(argument.type)
|
||||
return possible.mapNotNull { overload ->
|
||||
// Dispatch receivers must always match exactly
|
||||
if (function.dispatchReceiverParameter?.type != overload.owner.dispatchReceiverParameter?.type) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
private fun isStringSupertype(type: IrType): Boolean =
|
||||
context.irBuiltIns.stringType.isSubtypeOf(type, irTypeSystemContext)
|
||||
// Extension receiver may only be assignable
|
||||
if (!function.extensionReceiverParameter?.type.isAssignableTo(overload.owner.extensionReceiverParameter?.type)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
private fun IrType?.isAssignableTo(type: IrType?): Boolean {
|
||||
if (this != null && type != null) {
|
||||
if (isSubtypeOf(type, irTypeSystemContext)) return true
|
||||
val superTypes = (type.classifierOrNull as? IrTypeParameterSymbol)?.owner?.superTypes
|
||||
return superTypes != null && superTypes.all { isSubtypeOf(it, irTypeSystemContext) }
|
||||
} else {
|
||||
return this == null && type == null
|
||||
val parameters = overload.owner.valueParameters
|
||||
if (parameters.size !in values.size..values.size + 1) return@mapNotNull null
|
||||
if (!parameters.zip(values).all { (param, value) -> value.type.isAssignableTo(param.type) }) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val messageParameter = parameters.last()
|
||||
return@mapNotNull when {
|
||||
isStringSupertype(messageParameter.type) -> SimpleFunctionDelegate(overload, messageParameter)
|
||||
isStringFunction(messageParameter.type) -> LambdaFunctionDelegate(overload, messageParameter)
|
||||
isStringJavaSupplierFunction(messageParameter.type) ->
|
||||
SamConversionLambdaFunctionDelegate(overload, messageParameter)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageCollector.info(expression: IrElement, message: String) {
|
||||
report(expression, CompilerMessageSeverity.INFO, message)
|
||||
}
|
||||
private fun isStringFunction(type: IrType): Boolean =
|
||||
type.isFunctionOrKFunction() && type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
|
||||
private fun MessageCollector.warn(expression: IrElement, message: String) {
|
||||
report(expression, CompilerMessageSeverity.WARNING, message)
|
||||
}
|
||||
private fun isStringJavaSupplierFunction(type: IrType): Boolean {
|
||||
val javaSupplier = context.referenceClass(ClassId.topLevel(FqName("java.util.function.Supplier")))
|
||||
return javaSupplier != null && type.isSubtypeOfClass(javaSupplier) &&
|
||||
type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
}
|
||||
|
||||
private fun MessageCollector.report(expression: IrElement, severity: CompilerMessageSeverity, message: String) {
|
||||
report(severity, message, sourceFile.getCompilerMessageLocation(expression))
|
||||
}
|
||||
private fun isStringSupertype(argument: IrTypeArgument): Boolean =
|
||||
argument is IrTypeProjection && isStringSupertype(argument.type)
|
||||
|
||||
private fun isStringSupertype(type: IrType): Boolean =
|
||||
context.irBuiltIns.stringType.isSubtypeOf(type, irTypeSystemContext)
|
||||
|
||||
private fun IrType?.isAssignableTo(type: IrType?): Boolean {
|
||||
if (this != null && type != null) {
|
||||
if (isSubtypeOf(type, irTypeSystemContext)) return true
|
||||
val superTypes = (type.classifierOrNull as? IrTypeParameterSymbol)?.owner?.superTypes
|
||||
return superTypes != null && superTypes.all { isSubtypeOf(it, irTypeSystemContext) }
|
||||
} else {
|
||||
return this == null && type == null
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageCollector.info(expression: IrElement, message: String) {
|
||||
report(expression, CompilerMessageSeverity.INFO, message)
|
||||
}
|
||||
|
||||
private fun MessageCollector.warn(expression: IrElement, message: String) {
|
||||
report(expression, CompilerMessageSeverity.WARNING, message)
|
||||
}
|
||||
|
||||
private fun MessageCollector.report(expression: IrElement, severity: CompilerMessageSeverity, message: String) {
|
||||
report(severity, message, sourceFile.getCompilerMessageLocation(expression))
|
||||
}
|
||||
}
|
||||
|
||||
val IrFunction.callableId: CallableId
|
||||
get() {
|
||||
val parentClass = parent as? IrClass
|
||||
val classId = parentClass?.classId
|
||||
return if (classId != null && !parentClass.isFileClass) {
|
||||
CallableId(classId, name)
|
||||
} else {
|
||||
CallableId(parent.kotlinFqName, name)
|
||||
get() {
|
||||
val parentClass = parent as? IrClass
|
||||
val classId = parentClass?.classId
|
||||
return if (classId != null && !parentClass.isFileClass) {
|
||||
CallableId(classId, name)
|
||||
} else {
|
||||
CallableId(parent.kotlinFqName, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -19,21 +19,21 @@
|
||||
|
||||
package org.jetbrains.kotlin.powerassert
|
||||
|
||||
import org.jetbrains.kotlin.powerassert.diagram.SourceFile
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.powerassert.diagram.SourceFile
|
||||
|
||||
class PowerAssertIrGenerationExtension(
|
||||
private val messageCollector: MessageCollector,
|
||||
private val functions: Set<FqName>,
|
||||
private val messageCollector: MessageCollector,
|
||||
private val functions: Set<FqName>,
|
||||
) : IrGenerationExtension {
|
||||
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
|
||||
for (file in moduleFragment.files) {
|
||||
PowerAssertCallTransformer(SourceFile(file), pluginContext, messageCollector, functions)
|
||||
.visitFile(file)
|
||||
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
|
||||
for (file in moduleFragment.files) {
|
||||
PowerAssertCallTransformer(SourceFile(file), pluginContext, messageCollector, functions)
|
||||
.visitFile(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-29
@@ -30,36 +30,36 @@ import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
|
||||
interface FunctionDelegate {
|
||||
val function: IrFunction
|
||||
val messageParameter: IrValueParameter
|
||||
val function: IrFunction
|
||||
val messageParameter: IrValueParameter
|
||||
|
||||
fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression
|
||||
fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression
|
||||
|
||||
fun IrBuilderWithScope.irCallCopy(
|
||||
overload: IrSimpleFunctionSymbol,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression {
|
||||
return irCall(overload, type = original.type).apply {
|
||||
this.dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent)
|
||||
this.extensionReceiver = (extensionReceiver ?: original.extensionReceiver)?.deepCopyWithSymbols(parent)
|
||||
for (i in 0 until original.typeArgumentsCount) {
|
||||
putTypeArgument(i, original.getTypeArgument(i))
|
||||
}
|
||||
for ((i, argument) in valueArguments.withIndex()) {
|
||||
putValueArgument(i, argument?.deepCopyWithSymbols(parent))
|
||||
}
|
||||
putValueArgument(valueArguments.size, messageArgument.deepCopyWithSymbols(parent))
|
||||
fun IrBuilderWithScope.irCallCopy(
|
||||
overload: IrSimpleFunctionSymbol,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression {
|
||||
return irCall(overload, type = original.type).apply {
|
||||
this.dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent)
|
||||
this.extensionReceiver = (extensionReceiver ?: original.extensionReceiver)?.deepCopyWithSymbols(parent)
|
||||
for (i in 0 until original.typeArgumentsCount) {
|
||||
putTypeArgument(i, original.getTypeArgument(i))
|
||||
}
|
||||
for ((i, argument) in valueArguments.withIndex()) {
|
||||
putValueArgument(i, argument?.deepCopyWithSymbols(parent))
|
||||
}
|
||||
putValueArgument(valueArguments.size, messageArgument.deepCopyWithSymbols(parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-23
@@ -19,38 +19,38 @@
|
||||
|
||||
package org.jetbrains.kotlin.powerassert.delegate
|
||||
|
||||
import org.jetbrains.kotlin.powerassert.irLambda
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.irReturn
|
||||
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.powerassert.irLambda
|
||||
|
||||
class LambdaFunctionDelegate(
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
) : FunctionDelegate {
|
||||
override val function = overload.owner
|
||||
override val function = overload.owner
|
||||
|
||||
override fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression = with(builder) {
|
||||
val expression = irLambda(context.irBuiltIns.stringType, messageParameter.type) {
|
||||
+irReturn(messageArgument)
|
||||
override fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression = with(builder) {
|
||||
val expression = irLambda(context.irBuiltIns.stringType, messageParameter.type) {
|
||||
+irReturn(messageArgument)
|
||||
}
|
||||
irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = expression,
|
||||
)
|
||||
}
|
||||
irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = expression,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+24
-24
@@ -19,7 +19,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.powerassert.delegate
|
||||
|
||||
import org.jetbrains.kotlin.powerassert.irLambda
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.irReturn
|
||||
import org.jetbrains.kotlin.ir.builders.irSamConversion
|
||||
@@ -27,32 +26,33 @@ import org.jetbrains.kotlin.ir.declarations.IrValueParameter
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.powerassert.irLambda
|
||||
|
||||
class SamConversionLambdaFunctionDelegate(
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
) : FunctionDelegate {
|
||||
override val function = overload.owner
|
||||
override val function = overload.owner
|
||||
|
||||
override fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression = with(builder) {
|
||||
val lambda = irLambda(context.irBuiltIns.stringType, messageParameter.type) {
|
||||
+irReturn(messageArgument)
|
||||
override fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression = with(builder) {
|
||||
val lambda = irLambda(context.irBuiltIns.stringType, messageParameter.type) {
|
||||
+irReturn(messageArgument)
|
||||
}
|
||||
val expression = irSamConversion(lambda, messageParameter.type)
|
||||
irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = expression,
|
||||
)
|
||||
}
|
||||
val expression = irSamConversion(lambda, messageParameter.type)
|
||||
irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = expression,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-18
@@ -26,24 +26,24 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
|
||||
class SimpleFunctionDelegate(
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
) : FunctionDelegate {
|
||||
override val function = overload.owner
|
||||
override val function = overload.owner
|
||||
|
||||
override fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression = builder.irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = messageArgument,
|
||||
)
|
||||
override fun buildCall(
|
||||
builder: IrBuilderWithScope,
|
||||
original: IrCall,
|
||||
dispatchReceiver: IrExpression?,
|
||||
extensionReceiver: IrExpression?,
|
||||
valueArguments: List<IrExpression?>,
|
||||
messageArgument: IrExpression,
|
||||
): IrExpression = builder.irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = messageArgument,
|
||||
)
|
||||
}
|
||||
|
||||
+62
-66
@@ -19,41 +19,37 @@
|
||||
|
||||
package org.jetbrains.kotlin.powerassert.diagram
|
||||
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||
import org.jetbrains.kotlin.ir.builders.irFalse
|
||||
import org.jetbrains.kotlin.ir.builders.irIfThenElse
|
||||
import org.jetbrains.kotlin.ir.builders.irTrue
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
|
||||
fun IrBuilderWithScope.buildDiagramNesting(
|
||||
root: Node,
|
||||
variables: List<IrTemporaryVariable> = emptyList(),
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
root: Node,
|
||||
variables: List<IrTemporaryVariable> = emptyList(),
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
): IrExpression {
|
||||
return buildExpression(root, variables) { argument, subStack ->
|
||||
call(argument, subStack)
|
||||
}
|
||||
return buildExpression(root, variables) { argument, subStack ->
|
||||
call(argument, subStack)
|
||||
}
|
||||
}
|
||||
|
||||
fun IrBuilderWithScope.buildDiagramNestingNullable(
|
||||
root: Node?,
|
||||
variables: List<IrTemporaryVariable> = emptyList(),
|
||||
call: IrBuilderWithScope.(IrExpression?, List<IrTemporaryVariable>) -> IrExpression,
|
||||
root: Node?,
|
||||
variables: List<IrTemporaryVariable> = emptyList(),
|
||||
call: IrBuilderWithScope.(IrExpression?, List<IrTemporaryVariable>) -> IrExpression,
|
||||
): IrExpression {
|
||||
return if (root != null) buildDiagramNesting(root, variables, call) else call(null, variables)
|
||||
return if (root != null) buildDiagramNesting(root, variables, call) else call(null, variables)
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.buildExpression(
|
||||
node: Node,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
node: Node,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
): IrExpression = when (node) {
|
||||
is ExpressionNode -> add(node, variables, call)
|
||||
is AndNode -> nest(node, 0, variables, call)
|
||||
is OrNode -> nest(node, 0, variables, call)
|
||||
else -> TODO("Unknown node type=$node")
|
||||
is ExpressionNode -> add(node, variables, call)
|
||||
is AndNode -> nest(node, 0, variables, call)
|
||||
is OrNode -> nest(node, 0, variables, call)
|
||||
else -> TODO("Unknown node type=$node")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,17 +66,17 @@ private fun IrBuilderWithScope.buildExpression(
|
||||
* ```
|
||||
*/
|
||||
private fun IrBuilderWithScope.add(
|
||||
node: ExpressionNode,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
node: ExpressionNode,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
): IrExpression {
|
||||
return irBlock {
|
||||
val head = node.expressions.first().deepCopyWithSymbols(scope.getLocalDeclarationParent())
|
||||
val expressions = (buildTree(head) as ExpressionNode).expressions
|
||||
val transformer = IrTemporaryExtractionTransformer(this@irBlock, expressions.toSet())
|
||||
val transformed = expressions.first().transform(transformer, null)
|
||||
+call(transformed, variables + transformer.variables)
|
||||
}
|
||||
return irBlock {
|
||||
val head = node.expressions.first().deepCopyWithSymbols(scope.getLocalDeclarationParent())
|
||||
val expressions = (buildTree(head) as ExpressionNode).expressions
|
||||
val transformer = IrTemporaryExtractionTransformer(this@irBlock, expressions.toSet())
|
||||
val transformed = expressions.first().transform(transformer, null)
|
||||
+call(transformed, variables + transformer.variables)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,25 +96,25 @@ private fun IrBuilderWithScope.add(
|
||||
* ```
|
||||
*/
|
||||
private fun IrBuilderWithScope.nest(
|
||||
node: AndNode,
|
||||
index: Int,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
node: AndNode,
|
||||
index: Int,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
): IrExpression {
|
||||
val children = node.children
|
||||
val child = children[index]
|
||||
return buildExpression(child, variables) { argument, newVariables ->
|
||||
if (index + 1 == children.size) {
|
||||
call(argument, newVariables) // last expression, result is false
|
||||
} else {
|
||||
irIfThenElse(
|
||||
context.irBuiltIns.anyType,
|
||||
argument,
|
||||
nest(node, index + 1, newVariables, call), // more expressions, continue nesting
|
||||
call(irFalse(), newVariables), // short-circuit result to false
|
||||
)
|
||||
val children = node.children
|
||||
val child = children[index]
|
||||
return buildExpression(child, variables) { argument, newVariables ->
|
||||
if (index + 1 == children.size) {
|
||||
call(argument, newVariables) // last expression, result is false
|
||||
} else {
|
||||
irIfThenElse(
|
||||
context.irBuiltIns.anyType,
|
||||
argument,
|
||||
nest(node, index + 1, newVariables, call), // more expressions, continue nesting
|
||||
call(irFalse(), newVariables), // short-circuit result to false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,23 +134,23 @@ private fun IrBuilderWithScope.nest(
|
||||
* ```
|
||||
*/
|
||||
private fun IrBuilderWithScope.nest(
|
||||
node: OrNode,
|
||||
index: Int,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
node: OrNode,
|
||||
index: Int,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
): IrExpression {
|
||||
val children = node.children
|
||||
val child = children[index]
|
||||
return buildExpression(child, variables) { argument, newVariables ->
|
||||
if (index + 1 == children.size) {
|
||||
call(argument, newVariables) // last expression, result is false
|
||||
} else {
|
||||
irIfThenElse(
|
||||
context.irBuiltIns.anyType,
|
||||
argument,
|
||||
call(irTrue(), newVariables), // short-circuit result to true
|
||||
nest(node, index + 1, newVariables, call), // more expressions, continue nesting
|
||||
)
|
||||
val children = node.children
|
||||
val child = children[index]
|
||||
return buildExpression(child, variables) { argument, newVariables ->
|
||||
if (index + 1 == children.size) {
|
||||
call(argument, newVariables) // last expression, result is false
|
||||
} else {
|
||||
irIfThenElse(
|
||||
context.irBuiltIns.anyType,
|
||||
argument,
|
||||
call(irTrue(), newVariables), // short-circuit result to true
|
||||
nest(node, index + 1, newVariables, call), // more expressions, continue nesting
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+137
-146
@@ -20,179 +20,170 @@
|
||||
package org.jetbrains.kotlin.powerassert.diagram
|
||||
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
import org.jetbrains.kotlin.ir.expressions.IrContainerExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrVararg
|
||||
import org.jetbrains.kotlin.ir.expressions.IrWhen
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.util.dumpKotlinLike
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
|
||||
abstract class Node {
|
||||
private val _children = mutableListOf<Node>()
|
||||
val children: List<Node> get() = _children
|
||||
private val _children = mutableListOf<Node>()
|
||||
val children: List<Node> get() = _children
|
||||
|
||||
fun addChild(node: Node) {
|
||||
_children.add(node)
|
||||
}
|
||||
|
||||
fun dump(): String = buildString {
|
||||
dump(this, 0)
|
||||
}
|
||||
|
||||
private fun dump(builder: StringBuilder, indent: Int) {
|
||||
builder.append(" ".repeat(indent)).append(this).appendLine()
|
||||
for (child in children) {
|
||||
child.dump(builder, indent + 1)
|
||||
fun addChild(node: Node) {
|
||||
_children.add(node)
|
||||
}
|
||||
|
||||
fun dump(): String = buildString {
|
||||
dump(this, 0)
|
||||
}
|
||||
|
||||
private fun dump(builder: StringBuilder, indent: Int) {
|
||||
builder.append(" ".repeat(indent)).append(this).appendLine()
|
||||
for (child in children) {
|
||||
child.dump(builder, indent + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AndNode : Node() {
|
||||
override fun toString() = "AndNode"
|
||||
override fun toString() = "AndNode"
|
||||
}
|
||||
|
||||
class OrNode : Node() {
|
||||
override fun toString() = "OrNode"
|
||||
override fun toString() = "OrNode"
|
||||
}
|
||||
|
||||
class ExpressionNode : Node() {
|
||||
private val _expressions = mutableListOf<IrExpression>()
|
||||
val expressions: List<IrExpression> = _expressions
|
||||
private val _expressions = mutableListOf<IrExpression>()
|
||||
val expressions: List<IrExpression> = _expressions
|
||||
|
||||
fun add(expression: IrExpression) {
|
||||
_expressions.add(expression)
|
||||
}
|
||||
fun add(expression: IrExpression) {
|
||||
_expressions.add(expression)
|
||||
}
|
||||
|
||||
override fun toString() = "ExpressionNode(${_expressions.map { it.dumpKotlinLike() }})"
|
||||
override fun toString() = "ExpressionNode(${_expressions.map { it.dumpKotlinLike() }})"
|
||||
}
|
||||
|
||||
fun buildTree(expression: IrExpression): Node? {
|
||||
class RootNode : Node() {
|
||||
override fun toString() = "RootNode"
|
||||
}
|
||||
class RootNode : Node() {
|
||||
override fun toString() = "RootNode"
|
||||
}
|
||||
|
||||
val tree = RootNode()
|
||||
expression.accept(
|
||||
object : IrElementVisitor<Unit, Node> {
|
||||
override fun visitElement(element: IrElement, data: Node) {
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitExpression(expression: IrExpression, data: Node) {
|
||||
if (expression is IrFunctionExpression) return // Do not transform lambda expressions, especially their body
|
||||
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
node.add(expression)
|
||||
expression.acceptChildren(this, node)
|
||||
}
|
||||
|
||||
override fun visitContainerExpression(expression: IrContainerExpression, data: Node) {
|
||||
if (expression.origin is IrStatementOrigin.SAFE_CALL) {
|
||||
// Null safe expressions can be correctly navigated
|
||||
super.visitContainerExpression(expression, data)
|
||||
} else {
|
||||
// Everything else is considered unsafe and terminates the expression tree
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
node.add(expression)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: Node) {
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
if (expression.operator in setOf(IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF)) {
|
||||
// Only include `is` and `!is` checks
|
||||
node.add(expression)
|
||||
}
|
||||
|
||||
expression.acceptChildren(this, node)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: Node) {
|
||||
if (expression.symbol.owner.name.asString() == "EQEQ" && expression.origin == IrStatementOrigin.EXCLEQ) {
|
||||
// Skip the EQEQ part of a EXCLEQ call
|
||||
expression.acceptChildren(this, data)
|
||||
} else if (expression.origin == IrStatementOrigin.NOT_IN) {
|
||||
// Exclude the wrapped "contains" call for `!in` operator expressions and only display the final result
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
node.add(expression)
|
||||
expression.dispatchReceiver!!.acceptChildren(this, node)
|
||||
} else {
|
||||
super.visitCall(expression, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitVararg(expression: IrVararg, data: Node) {
|
||||
// Skip processing of vararg array
|
||||
expression.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitConst(expression: IrConst<*>, data: Node) {
|
||||
// Do not include constants
|
||||
}
|
||||
|
||||
override fun visitWhen(expression: IrWhen, data: Node) {
|
||||
when (expression.origin) {
|
||||
IrStatementOrigin.ANDAND -> {
|
||||
// flatten `&&` expressions to be at the same level
|
||||
val node = data as? AndNode ?: AndNode().also { data.addChild(it) }
|
||||
|
||||
require(expression.branches.size == 2)
|
||||
val thenBranch = expression.branches[0]
|
||||
|
||||
thenBranch.condition.accept(this, node)
|
||||
thenBranch.result.accept(this, node)
|
||||
|
||||
val elseBranchCondition = expression.branches[1].condition
|
||||
val elseBranchResult = expression.branches[1].result
|
||||
|
||||
if (elseBranchCondition !is IrConst<*> || elseBranchCondition.value != true) {
|
||||
elseBranchCondition.accept(this, node)
|
||||
val tree = RootNode()
|
||||
expression.accept(
|
||||
object : IrElementVisitor<Unit, Node> {
|
||||
override fun visitElement(element: IrElement, data: Node) {
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
if (elseBranchResult !is IrConst<*> || elseBranchResult.value != false) {
|
||||
elseBranchResult.accept(this, node)
|
||||
}
|
||||
}
|
||||
IrStatementOrigin.OROR -> {
|
||||
// flatten `||` expressions to be at the same level
|
||||
val node = data as? OrNode ?: OrNode().also { data.addChild(it) }
|
||||
override fun visitExpression(expression: IrExpression, data: Node) {
|
||||
if (expression is IrFunctionExpression) return // Do not transform lambda expressions, especially their body
|
||||
|
||||
require(expression.branches.size == 2)
|
||||
val thenBranchCondition = expression.branches[0].condition
|
||||
val thenBranchResult = expression.branches[0].result
|
||||
val elseBranchCondition = expression.branches[1].condition
|
||||
val elseBranchResult = expression.branches[1].result
|
||||
|
||||
thenBranchCondition.accept(this, node)
|
||||
|
||||
if (thenBranchResult !is IrConst<*> || thenBranchResult.value != true) {
|
||||
thenBranchResult.accept(this, node)
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
node.add(expression)
|
||||
expression.acceptChildren(this, node)
|
||||
}
|
||||
|
||||
if (elseBranchCondition !is IrConst<*> || elseBranchCondition.value != true) {
|
||||
elseBranchCondition.accept(this, node)
|
||||
override fun visitContainerExpression(expression: IrContainerExpression, data: Node) {
|
||||
if (expression.origin is IrStatementOrigin.SAFE_CALL) {
|
||||
// Null safe expressions can be correctly navigated
|
||||
super.visitContainerExpression(expression, data)
|
||||
} else {
|
||||
// Everything else is considered unsafe and terminates the expression tree
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
node.add(expression)
|
||||
}
|
||||
}
|
||||
|
||||
if (elseBranchResult !is IrConst<*> || elseBranchResult.value != false) {
|
||||
elseBranchResult.accept(this, node)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Add as basic expression and terminate
|
||||
// TODO this has to be broken and not work in all cases...
|
||||
ExpressionNode().also { data.addChild(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tree,
|
||||
)
|
||||
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: Node) {
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
if (expression.operator in setOf(IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF)) {
|
||||
// Only include `is` and `!is` checks
|
||||
node.add(expression)
|
||||
}
|
||||
|
||||
return tree.children.singleOrNull()
|
||||
expression.acceptChildren(this, node)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: Node) {
|
||||
if (expression.symbol.owner.name.asString() == "EQEQ" && expression.origin == IrStatementOrigin.EXCLEQ) {
|
||||
// Skip the EQEQ part of a EXCLEQ call
|
||||
expression.acceptChildren(this, data)
|
||||
} else if (expression.origin == IrStatementOrigin.NOT_IN) {
|
||||
// Exclude the wrapped "contains" call for `!in` operator expressions and only display the final result
|
||||
val node = data as? ExpressionNode ?: ExpressionNode().also { data.addChild(it) }
|
||||
node.add(expression)
|
||||
expression.dispatchReceiver!!.acceptChildren(this, node)
|
||||
} else {
|
||||
super.visitCall(expression, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitVararg(expression: IrVararg, data: Node) {
|
||||
// Skip processing of vararg array
|
||||
expression.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitConst(expression: IrConst<*>, data: Node) {
|
||||
// Do not include constants
|
||||
}
|
||||
|
||||
override fun visitWhen(expression: IrWhen, data: Node) {
|
||||
when (expression.origin) {
|
||||
IrStatementOrigin.ANDAND -> {
|
||||
// flatten `&&` expressions to be at the same level
|
||||
val node = data as? AndNode ?: AndNode().also { data.addChild(it) }
|
||||
|
||||
require(expression.branches.size == 2)
|
||||
val thenBranch = expression.branches[0]
|
||||
|
||||
thenBranch.condition.accept(this, node)
|
||||
thenBranch.result.accept(this, node)
|
||||
|
||||
val elseBranchCondition = expression.branches[1].condition
|
||||
val elseBranchResult = expression.branches[1].result
|
||||
|
||||
if (elseBranchCondition !is IrConst<*> || elseBranchCondition.value != true) {
|
||||
elseBranchCondition.accept(this, node)
|
||||
}
|
||||
|
||||
if (elseBranchResult !is IrConst<*> || elseBranchResult.value != false) {
|
||||
elseBranchResult.accept(this, node)
|
||||
}
|
||||
}
|
||||
IrStatementOrigin.OROR -> {
|
||||
// flatten `||` expressions to be at the same level
|
||||
val node = data as? OrNode ?: OrNode().also { data.addChild(it) }
|
||||
|
||||
require(expression.branches.size == 2)
|
||||
val thenBranchCondition = expression.branches[0].condition
|
||||
val thenBranchResult = expression.branches[0].result
|
||||
val elseBranchCondition = expression.branches[1].condition
|
||||
val elseBranchResult = expression.branches[1].result
|
||||
|
||||
thenBranchCondition.accept(this, node)
|
||||
|
||||
if (thenBranchResult !is IrConst<*> || thenBranchResult.value != true) {
|
||||
thenBranchResult.accept(this, node)
|
||||
}
|
||||
|
||||
if (elseBranchCondition !is IrConst<*> || elseBranchCondition.value != true) {
|
||||
elseBranchCondition.accept(this, node)
|
||||
}
|
||||
|
||||
if (elseBranchResult !is IrConst<*> || elseBranchResult.value != false) {
|
||||
elseBranchResult.accept(this, node)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Add as basic expression and terminate
|
||||
// TODO this has to be broken and not work in all cases...
|
||||
ExpressionNode().also { data.addChild(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tree,
|
||||
)
|
||||
|
||||
return tree.children.singleOrNull()
|
||||
}
|
||||
|
||||
+112
-120
@@ -19,7 +19,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.powerassert.diagram
|
||||
|
||||
import org.jetbrains.kotlin.powerassert.irString
|
||||
import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.SourceRangeInfo
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
@@ -27,15 +26,8 @@ import org.jetbrains.kotlin.ir.builders.irConcat
|
||||
import org.jetbrains.kotlin.ir.builders.irGet
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrVariable
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConstKind
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
|
||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
|
||||
import org.jetbrains.kotlin.ir.expressions.addArgument
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.powerassert.irString
|
||||
|
||||
fun IrBuilderWithScope.irDiagramString(
|
||||
sourceFile: SourceFile,
|
||||
@@ -43,62 +35,62 @@ fun IrBuilderWithScope.irDiagramString(
|
||||
call: IrCall,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
): IrExpression {
|
||||
val callInfo = sourceFile.getSourceRangeInfo(call)
|
||||
val callIndent = callInfo.startColumnNumber
|
||||
val callInfo = sourceFile.getSourceRangeInfo(call)
|
||||
val callIndent = callInfo.startColumnNumber
|
||||
|
||||
val stackValues = variables.map { it.toValueDisplay(sourceFile, callIndent, callInfo) }
|
||||
val stackValues = variables.map { it.toValueDisplay(sourceFile, callIndent, callInfo) }
|
||||
|
||||
val valuesByRow = stackValues.groupBy { it.row }
|
||||
val rows = sourceFile.getText(callInfo)
|
||||
.replace("\n" + " ".repeat(callIndent), "\n") // Remove additional indentation
|
||||
.split("\n")
|
||||
val valuesByRow = stackValues.groupBy { it.row }
|
||||
val rows = sourceFile.getText(callInfo)
|
||||
.replace("\n" + " ".repeat(callIndent), "\n") // Remove additional indentation
|
||||
.split("\n")
|
||||
|
||||
return irConcat().apply {
|
||||
if (prefix != null) addArgument(prefix)
|
||||
return irConcat().apply {
|
||||
if (prefix != null) addArgument(prefix)
|
||||
|
||||
for ((row, rowSource) in rows.withIndex()) {
|
||||
val rowValues = valuesByRow[row]?.let { values -> values.sortedBy { it.indent } } ?: emptyList()
|
||||
val indentations = rowValues.map { it.indent }
|
||||
for ((row, rowSource) in rows.withIndex()) {
|
||||
val rowValues = valuesByRow[row]?.let { values -> values.sortedBy { it.indent } } ?: emptyList()
|
||||
val indentations = rowValues.map { it.indent }
|
||||
|
||||
addArgument(
|
||||
irString {
|
||||
if (row != 0 || prefix != null) appendLine()
|
||||
append(rowSource)
|
||||
if (indentations.isNotEmpty()) {
|
||||
appendLine()
|
||||
var last = -1
|
||||
for (i in indentations) {
|
||||
if (i > last) indent(i - last - 1).append("|")
|
||||
last = i
|
||||
addArgument(
|
||||
irString {
|
||||
if (row != 0 || prefix != null) appendLine()
|
||||
append(rowSource)
|
||||
if (indentations.isNotEmpty()) {
|
||||
appendLine()
|
||||
var last = -1
|
||||
for (i in indentations) {
|
||||
if (i > last) indent(i - last - 1).append("|")
|
||||
last = i
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
for (tmp in rowValues.asReversed()) {
|
||||
addArgument(
|
||||
irString {
|
||||
appendLine()
|
||||
var last = -1
|
||||
for (i in indentations) {
|
||||
if (i == tmp.indent) break
|
||||
if (i > last) indent(i - last - 1).append("|")
|
||||
last = i
|
||||
}
|
||||
indent(tmp.indent - last - 1)
|
||||
},
|
||||
)
|
||||
addArgument(irGet(tmp.value))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
for (tmp in rowValues.asReversed()) {
|
||||
addArgument(
|
||||
irString {
|
||||
appendLine()
|
||||
var last = -1
|
||||
for (i in indentations) {
|
||||
if (i == tmp.indent) break
|
||||
if (i > last) indent(i - last - 1).append("|")
|
||||
last = i
|
||||
}
|
||||
indent(tmp.indent - last - 1)
|
||||
},
|
||||
)
|
||||
addArgument(irGet(tmp.value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class ValueDisplay(
|
||||
val value: IrVariable,
|
||||
val indent: Int,
|
||||
val row: Int,
|
||||
val source: String,
|
||||
val value: IrVariable,
|
||||
val indent: Int,
|
||||
val row: Int,
|
||||
val source: String,
|
||||
)
|
||||
|
||||
private fun IrTemporaryVariable.toValueDisplay(
|
||||
@@ -106,24 +98,24 @@ private fun IrTemporaryVariable.toValueDisplay(
|
||||
callIndent: Int,
|
||||
originalInfo: SourceRangeInfo,
|
||||
): ValueDisplay {
|
||||
val info = fileSource.getSourceRangeInfo(original)
|
||||
var indent = info.startColumnNumber - callIndent
|
||||
var row = info.startLineNumber - originalInfo.startLineNumber
|
||||
val info = fileSource.getSourceRangeInfo(original)
|
||||
var indent = info.startColumnNumber - callIndent
|
||||
var row = info.startLineNumber - originalInfo.startLineNumber
|
||||
|
||||
val source = fileSource.getText(info)
|
||||
.replace("\n" + " ".repeat(callIndent), "\n") // Remove additional indentation
|
||||
val columnOffset = findDisplayOffset(fileSource, original, source)
|
||||
val source = fileSource.getText(info)
|
||||
.replace("\n" + " ".repeat(callIndent), "\n") // Remove additional indentation
|
||||
val columnOffset = findDisplayOffset(fileSource, original, source)
|
||||
|
||||
val prefix = source.substring(0, columnOffset)
|
||||
val rowShift = prefix.count { it == '\n' }
|
||||
if (rowShift == 0) {
|
||||
indent += columnOffset
|
||||
} else {
|
||||
row += rowShift
|
||||
indent = columnOffset - (prefix.lastIndexOf('\n') + 1)
|
||||
}
|
||||
val prefix = source.substring(0, columnOffset)
|
||||
val rowShift = prefix.count { it == '\n' }
|
||||
if (rowShift == 0) {
|
||||
indent += columnOffset
|
||||
} else {
|
||||
row += rowShift
|
||||
indent = columnOffset - (prefix.lastIndexOf('\n') + 1)
|
||||
}
|
||||
|
||||
return ValueDisplay(temporary, indent, row, source)
|
||||
return ValueDisplay(temporary, indent, row, source)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,11 +155,11 @@ private fun findDisplayOffset(
|
||||
expression: IrExpression,
|
||||
source: String,
|
||||
): Int {
|
||||
return when (expression) {
|
||||
is IrMemberAccessExpression<*> -> memberAccessOffset(sourceFile, expression, source)
|
||||
is IrTypeOperatorCall -> typeOperatorOffset(expression, source)
|
||||
else -> 0
|
||||
}
|
||||
return when (expression) {
|
||||
is IrMemberAccessExpression<*> -> memberAccessOffset(sourceFile, expression, source)
|
||||
is IrTypeOperatorCall -> typeOperatorOffset(expression, source)
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun memberAccessOffset(
|
||||
@@ -175,57 +167,57 @@ private fun memberAccessOffset(
|
||||
expression: IrMemberAccessExpression<*>,
|
||||
source: String,
|
||||
): Int {
|
||||
when (expression.origin) {
|
||||
// special case to handle `value != null`
|
||||
IrStatementOrigin.EXCLEQ, IrStatementOrigin.EXCLEQEQ -> return source.indexOf("!=")
|
||||
// special case to handle `in` operator
|
||||
IrStatementOrigin.IN -> return source.indexOf(" in ") + 1
|
||||
// special case to handle `in` operator
|
||||
IrStatementOrigin.NOT_IN -> return source.indexOf(" !in ") + 1
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
val owner = expression.symbol.owner
|
||||
if (owner !is IrSimpleFunction) return 0
|
||||
|
||||
if (owner.isInfix || owner.isOperator || owner.origin == IrBuiltIns.BUILTIN_OPERATOR) {
|
||||
// Ignore single value operators
|
||||
val singleReceiver = (expression.dispatchReceiver != null) xor (expression.extensionReceiver != null)
|
||||
if (singleReceiver && expression.valueArgumentsCount == 0) return 0
|
||||
|
||||
// Start after the dispatcher or first argument
|
||||
val receiver = expression.dispatchReceiver
|
||||
?: expression.extensionReceiver
|
||||
?: expression.getValueArgument(0).takeIf { owner.origin == IrBuiltIns.BUILTIN_OPERATOR }
|
||||
?: return 0
|
||||
val expressionInfo = sourceFile.getSourceRangeInfo(expression)
|
||||
var offset = receiver.endOffset - expressionInfo.startOffset + 1
|
||||
if (receiver is IrConst<*> && receiver.kind == IrConstKind.String) offset++ // String constants don't include the quote
|
||||
if (offset < 0 || offset >= source.length) return 0 // infix function called using non-infix syntax
|
||||
|
||||
// Continue until there is a non-whitespace character
|
||||
while (source[offset].isWhitespace() || source[offset] == '.') {
|
||||
offset++
|
||||
if (offset >= source.length) return 0
|
||||
when (expression.origin) {
|
||||
// special case to handle `value != null`
|
||||
IrStatementOrigin.EXCLEQ, IrStatementOrigin.EXCLEQEQ -> return source.indexOf("!=")
|
||||
// special case to handle `in` operator
|
||||
IrStatementOrigin.IN -> return source.indexOf(" in ") + 1
|
||||
// special case to handle `in` operator
|
||||
IrStatementOrigin.NOT_IN -> return source.indexOf(" !in ") + 1
|
||||
else -> Unit
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
return 0
|
||||
val owner = expression.symbol.owner
|
||||
if (owner !is IrSimpleFunction) return 0
|
||||
|
||||
if (owner.isInfix || owner.isOperator || owner.origin == IrBuiltIns.BUILTIN_OPERATOR) {
|
||||
// Ignore single value operators
|
||||
val singleReceiver = (expression.dispatchReceiver != null) xor (expression.extensionReceiver != null)
|
||||
if (singleReceiver && expression.valueArgumentsCount == 0) return 0
|
||||
|
||||
// Start after the dispatcher or first argument
|
||||
val receiver = expression.dispatchReceiver
|
||||
?: expression.extensionReceiver
|
||||
?: expression.getValueArgument(0).takeIf { owner.origin == IrBuiltIns.BUILTIN_OPERATOR }
|
||||
?: return 0
|
||||
val expressionInfo = sourceFile.getSourceRangeInfo(expression)
|
||||
var offset = receiver.endOffset - expressionInfo.startOffset + 1
|
||||
if (receiver is IrConst<*> && receiver.kind == IrConstKind.String) offset++ // String constants don't include the quote
|
||||
if (offset < 0 || offset >= source.length) return 0 // infix function called using non-infix syntax
|
||||
|
||||
// Continue until there is a non-whitespace character
|
||||
while (source[offset].isWhitespace() || source[offset] == '.') {
|
||||
offset++
|
||||
if (offset >= source.length) return 0
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun typeOperatorOffset(
|
||||
expression: IrTypeOperatorCall,
|
||||
source: String,
|
||||
expression: IrTypeOperatorCall,
|
||||
source: String,
|
||||
): Int {
|
||||
return when (expression.operator) {
|
||||
IrTypeOperator.INSTANCEOF -> source.indexOf(" is ") + 1
|
||||
IrTypeOperator.NOT_INSTANCEOF -> source.indexOf(" !is ") + 1
|
||||
else -> 0
|
||||
}
|
||||
return when (expression.operator) {
|
||||
IrTypeOperator.INSTANCEOF -> source.indexOf(" is ") + 1
|
||||
IrTypeOperator.NOT_INSTANCEOF -> source.indexOf(" !is ") + 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
fun StringBuilder.indent(indentation: Int): StringBuilder {
|
||||
repeat(indentation) { append(" ") }
|
||||
return this
|
||||
repeat(indentation) { append(" ") }
|
||||
return this
|
||||
}
|
||||
|
||||
+15
-15
@@ -28,25 +28,25 @@ import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
|
||||
data class IrTemporaryVariable(
|
||||
val temporary: IrVariable,
|
||||
val original: IrExpression,
|
||||
val temporary: IrVariable,
|
||||
val original: IrExpression,
|
||||
)
|
||||
|
||||
class IrTemporaryExtractionTransformer(
|
||||
private val builder: IrStatementsBuilder<*>,
|
||||
private val transform: Set<IrExpression>,
|
||||
private val builder: IrStatementsBuilder<*>,
|
||||
private val transform: Set<IrExpression>,
|
||||
) : IrElementTransformerVoid() {
|
||||
private val _variables = mutableListOf<IrTemporaryVariable>()
|
||||
val variables: List<IrTemporaryVariable> = _variables
|
||||
private val _variables = mutableListOf<IrTemporaryVariable>()
|
||||
val variables: List<IrTemporaryVariable> = _variables
|
||||
|
||||
override fun visitExpression(expression: IrExpression): IrExpression {
|
||||
return if (expression in transform) {
|
||||
val copy = expression.deepCopyWithSymbols(builder.scope.getLocalDeclarationParent())
|
||||
val variable = builder.irTemporary(super.visitExpression(expression))
|
||||
_variables.add(IrTemporaryVariable(variable, copy))
|
||||
builder.irGet(variable)
|
||||
} else {
|
||||
super.visitExpression(expression)
|
||||
override fun visitExpression(expression: IrExpression): IrExpression {
|
||||
return if (expression in transform) {
|
||||
val copy = expression.deepCopyWithSymbols(builder.scope.getLocalDeclarationParent())
|
||||
val variable = builder.irTemporary(super.visitExpression(expression))
|
||||
_variables.add(IrTemporaryVariable(variable, copy))
|
||||
builder.irGet(variable)
|
||||
} else {
|
||||
super.visitExpression(expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-31
@@ -28,43 +28,43 @@ import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import java.io.File
|
||||
|
||||
data class SourceFile(
|
||||
private val irFile: IrFile,
|
||||
private val irFile: IrFile,
|
||||
) {
|
||||
private val source: String = File(irFile.path).readText()
|
||||
.replace("\r\n", "\n") // https://youtrack.jetbrains.com/issue/KT-41888
|
||||
private val source: String = File(irFile.path).readText()
|
||||
.replace("\r\n", "\n") // https://youtrack.jetbrains.com/issue/KT-41888
|
||||
|
||||
fun getSourceRangeInfo(element: IrElement): SourceRangeInfo {
|
||||
var range = element.startOffset..element.endOffset
|
||||
when (element) {
|
||||
is IrCall -> {
|
||||
val receiver = element.extensionReceiver ?: element.dispatchReceiver
|
||||
if (element.symbol.owner.isInfix && receiver != null) {
|
||||
// When an infix function is called *not* with infix notation, the startOffset will not include the receiver.
|
||||
// Force the range to include the receiver, so it is always present
|
||||
range = receiver.startOffset..element.endOffset
|
||||
fun getSourceRangeInfo(element: IrElement): SourceRangeInfo {
|
||||
var range = element.startOffset..element.endOffset
|
||||
when (element) {
|
||||
is IrCall -> {
|
||||
val receiver = element.extensionReceiver ?: element.dispatchReceiver
|
||||
if (element.symbol.owner.isInfix && receiver != null) {
|
||||
// When an infix function is called *not* with infix notation, the startOffset will not include the receiver.
|
||||
// Force the range to include the receiver, so it is always present
|
||||
range = receiver.startOffset..element.endOffset
|
||||
|
||||
// The offsets of the receiver will *not* include surrounding parentheses so these need to be checked for
|
||||
// manually.
|
||||
val substring = safeSubstring(receiver.startOffset - 1, receiver.endOffset + 1)
|
||||
if (substring.startsWith('(') && substring.endsWith(')')) {
|
||||
range = receiver.startOffset - 1..element.endOffset
|
||||
}
|
||||
// The offsets of the receiver will *not* include surrounding parentheses so these need to be checked for
|
||||
// manually.
|
||||
val substring = safeSubstring(receiver.startOffset - 1, receiver.endOffset + 1)
|
||||
if (substring.startsWith('(') && substring.endsWith(')')) {
|
||||
range = receiver.startOffset - 1..element.endOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return irFile.fileEntry.getSourceRangeInfo(range.first, range.last)
|
||||
}
|
||||
return irFile.fileEntry.getSourceRangeInfo(range.first, range.last)
|
||||
}
|
||||
|
||||
fun getText(info: SourceRangeInfo): String {
|
||||
return safeSubstring(info.startOffset, info.endOffset)
|
||||
}
|
||||
fun getText(info: SourceRangeInfo): String {
|
||||
return safeSubstring(info.startOffset, info.endOffset)
|
||||
}
|
||||
|
||||
private fun safeSubstring(start: Int, end: Int): String =
|
||||
source.substring(maxOf(start, 0), minOf(end, source.length))
|
||||
private fun safeSubstring(start: Int, end: Int): String =
|
||||
source.substring(maxOf(start, 0), minOf(end, source.length))
|
||||
|
||||
fun getCompilerMessageLocation(element: IrElement): CompilerMessageLocation {
|
||||
val info = getSourceRangeInfo(element)
|
||||
val lineContent = getText(info)
|
||||
return CompilerMessageLocation.create(irFile.path, info.startLineNumber, info.startColumnNumber, lineContent)!!
|
||||
}
|
||||
fun getCompilerMessageLocation(element: IrElement): CompilerMessageLocation {
|
||||
val info = getSourceRangeInfo(element)
|
||||
val lineContent = getText(info)
|
||||
return CompilerMessageLocation.create(irFile.path, info.startLineNumber, info.startColumnNumber, lineContent)!!
|
||||
}
|
||||
}
|
||||
|
||||
+19
-19
@@ -27,26 +27,26 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
|
||||
@AutoService(CommandLineProcessor::class)
|
||||
class PowerAssertCommandLineProcessor : CommandLineProcessor {
|
||||
override val pluginId: String = "com.bnorm.kotlin-power-assert"
|
||||
override val pluginId: String = "com.bnorm.kotlin-power-assert"
|
||||
|
||||
override val pluginOptions: Collection<CliOption> = listOf(
|
||||
CliOption(
|
||||
optionName = "function",
|
||||
valueDescription = "function full-qualified name",
|
||||
description = "fully qualified path of function to intercept",
|
||||
required = false, // TODO required for Kotlin/JS
|
||||
allowMultipleOccurrences = true,
|
||||
),
|
||||
)
|
||||
override val pluginOptions: Collection<CliOption> = listOf(
|
||||
CliOption(
|
||||
optionName = "function",
|
||||
valueDescription = "function full-qualified name",
|
||||
description = "fully qualified path of function to intercept",
|
||||
required = false, // TODO required for Kotlin/JS
|
||||
allowMultipleOccurrences = true,
|
||||
),
|
||||
)
|
||||
|
||||
override fun processOption(
|
||||
option: AbstractCliOption,
|
||||
value: String,
|
||||
configuration: CompilerConfiguration,
|
||||
) {
|
||||
return when (option.optionName) {
|
||||
"function" -> configuration.add(KEY_FUNCTIONS, value)
|
||||
else -> error("Unexpected config option ${option.optionName}")
|
||||
override fun processOption(
|
||||
option: AbstractCliOption,
|
||||
value: String,
|
||||
configuration: CompilerConfiguration,
|
||||
) {
|
||||
return when (option.optionName) {
|
||||
"function" -> configuration.add(KEY_FUNCTIONS, value)
|
||||
else -> error("Unexpected config option ${option.optionName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-11
@@ -27,24 +27,23 @@ import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.powerassert.PowerAssertIrGenerationExtension
|
||||
|
||||
val KEY_FUNCTIONS = CompilerConfigurationKey<List<String>>("fully-qualified function names")
|
||||
|
||||
@AutoService(CompilerPluginRegistrar::class)
|
||||
class PowerAssertCompilerPluginRegistrar(
|
||||
private val functions: Set<FqName>,
|
||||
private val functions: Set<FqName>,
|
||||
) : CompilerPluginRegistrar() {
|
||||
@Suppress("unused")
|
||||
constructor() : this(emptySet()) // Used by service loader
|
||||
@Suppress("unused")
|
||||
constructor() : this(emptySet()) // Used by service loader
|
||||
|
||||
override val supportsK2: Boolean = true
|
||||
override val supportsK2: Boolean = true
|
||||
|
||||
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
|
||||
val functions = configuration[KEY_FUNCTIONS]?.map { FqName(it) } ?: functions
|
||||
if (functions.isEmpty()) return
|
||||
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
|
||||
val functions = configuration[KEY_FUNCTIONS]?.map { FqName(it) } ?: functions
|
||||
if (functions.isEmpty()) return
|
||||
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
|
||||
IrGenerationExtension.registerExtension(PowerAssertIrGenerationExtension(messageCollector, functions.toSet()))
|
||||
}
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
|
||||
IrGenerationExtension.registerExtension(PowerAssertIrGenerationExtension(messageCollector, functions.toSet()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user