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