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