[PowerAssert] Reformat files

This commit is contained in:
Brian Norman
2023-11-17 10:48:18 -06:00
committed by Space Team
parent 2de0c8b23f
commit c3a60b127e
16 changed files with 751 additions and 798 deletions
@@ -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()
} }
@@ -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)
} }
}
} }
}
} }
@@ -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)
} }
@@ -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)
}
} }
}
@@ -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)
}
} }
}
} }
@@ -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))
}
} }
}
} }
@@ -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,
)
}
} }
@@ -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,
)
}
} }
@@ -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,
) )
} }
@@ -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
) )
}
} }
}
} }
@@ -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()
} }
@@ -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
} }
@@ -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)
}
} }
}
} }
@@ -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)!!
} }
} }
@@ -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}")
}
} }
}
} }
@@ -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()))
} }
} }