diff --git a/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradleExtension.kt b/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradleExtension.kt index 65b6168fb89..93c291b5f81 100644 --- a/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradleExtension.kt +++ b/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradleExtension.kt @@ -20,6 +20,6 @@ package org.jetbrains.kotlin.powerassert.gradle open class PowerAssertGradleExtension { - var functions: List = listOf("kotlin.assert") - var excludedSourceSets: List = listOf() + var functions: List = listOf("kotlin.assert") + var excludedSourceSets: List = listOf() } diff --git a/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradlePlugin.kt b/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradlePlugin.kt index 006b2f12f90..cc6e56b399a 100644 --- a/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradlePlugin.kt +++ b/libraries/tools/kotlin-power-assert/src/common/kotlin/org/jetbrains/kotlin/powerassert/gradle/PowerAssertGradlePlugin.kt @@ -27,33 +27,33 @@ import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact import org.jetbrains.kotlin.gradle.plugin.SubpluginOption class PowerAssertGradlePlugin : KotlinCompilerPluginSupportPlugin { - override fun apply(target: Project): Unit = with(target) { - extensions.create("kotlinPowerAssert", PowerAssertGradleExtension::class.java) - } - - override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { - val project = kotlinCompilation.target.project - val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java) - return extension.excludedSourceSets.none { it == kotlinCompilation.defaultSourceSet.name } - } - - override fun getCompilerPluginId(): String = "com.bnorm.kotlin-power-assert" - - override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( - groupId = BuildConfig.PLUGIN_GROUP_ID, - artifactId = BuildConfig.PLUGIN_ARTIFACT_ID, - version = BuildConfig.PLUGIN_VERSION, - ) - - override fun applyToCompilation( - kotlinCompilation: KotlinCompilation<*>, - ): Provider> { - val project = kotlinCompilation.target.project - val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java) - return project.provider { - extension.functions.map { - SubpluginOption(key = "function", value = it) - } + override fun apply(target: Project): Unit = with(target) { + extensions.create("kotlinPowerAssert", PowerAssertGradleExtension::class.java) + } + + override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { + val project = kotlinCompilation.target.project + val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java) + return extension.excludedSourceSets.none { it == kotlinCompilation.defaultSourceSet.name } + } + + override fun getCompilerPluginId(): String = "com.bnorm.kotlin-power-assert" + + override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( + groupId = BuildConfig.PLUGIN_GROUP_ID, + artifactId = BuildConfig.PLUGIN_ARTIFACT_ID, + version = BuildConfig.PLUGIN_VERSION, + ) + + override fun applyToCompilation( + kotlinCompilation: KotlinCompilation<*>, + ): Provider> { + val project = kotlinCompilation.target.project + val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java) + return project.provider { + extension.functions.map { + SubpluginOption(key = "function", value = it) + } + } } - } } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/IrUtils.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/IrUtils.kt index b4235fe6972..a418c3eebf1 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/IrUtils.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/IrUtils.kt @@ -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("") - 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("") + 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) } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertCallTransformer.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertCallTransformer.kt index 554b17ae09b..f30d31e50da 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertCallTransformer.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertCallTransformer.kt @@ -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, + private val sourceFile: SourceFile, + private val context: IrPluginContext, + private val messageCollector: MessageCollector, + private val functions: Set, ) : 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 + 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 - 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, - dispatchRoot: Node? = null, - extensionRoot: Node? = null, - ): IrExpression { - fun recursive( - index: Int, - dispatch: IrExpression?, - extension: IrExpression?, - arguments: List, - variables: List, + private fun DeclarationIrBuilder.diagram( + call: IrCall, + delegate: FunctionDelegate, + messageArgument: IrExpression?, + roots: List, + 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, + variables: List, + ): 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, - 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 { - 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, + 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 { + 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) + } } - } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertIrGenerationExtension.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertIrGenerationExtension.kt index 95f95697290..e7a95833661 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertIrGenerationExtension.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/PowerAssertIrGenerationExtension.kt @@ -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, + private val messageCollector: MessageCollector, + private val functions: Set, ) : 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) + } } - } } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/FunctionDelegate.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/FunctionDelegate.kt index cae1c227708..a6d41c50486 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/FunctionDelegate.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/FunctionDelegate.kt @@ -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, - messageArgument: IrExpression, - ): IrExpression + fun buildCall( + builder: IrBuilderWithScope, + original: IrCall, + dispatchReceiver: IrExpression?, + extensionReceiver: IrExpression?, + valueArguments: List, + messageArgument: IrExpression, + ): IrExpression - fun IrBuilderWithScope.irCallCopy( - overload: IrSimpleFunctionSymbol, - original: IrCall, - dispatchReceiver: IrExpression?, - extensionReceiver: IrExpression?, - valueArguments: List, - 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, + 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)) + } } - } } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/LambdaFunctionDelegate.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/LambdaFunctionDelegate.kt index 27a7e0a9879..993351bed35 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/LambdaFunctionDelegate.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/LambdaFunctionDelegate.kt @@ -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, - 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, + 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, - ) - } } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SamConversionLambdaFunctionDelegate.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SamConversionLambdaFunctionDelegate.kt index b2dc9c7508d..6a10a50413c 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SamConversionLambdaFunctionDelegate.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SamConversionLambdaFunctionDelegate.kt @@ -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, - 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, + 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, - ) - } } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SimpleFunctionDelegate.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SimpleFunctionDelegate.kt index 91afdaeff6e..8f7b90ad636 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SimpleFunctionDelegate.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/delegate/SimpleFunctionDelegate.kt @@ -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, - 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, + messageArgument: IrExpression, + ): IrExpression = builder.irCallCopy( + overload = overload, + original = original, + dispatchReceiver = dispatchReceiver, + extensionReceiver = extensionReceiver, + valueArguments = valueArguments, + messageArgument = messageArgument, + ) } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/DiagramBuilder.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/DiagramBuilder.kt index 3b80b0abb22..5f38d92f7cb 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/DiagramBuilder.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/DiagramBuilder.kt @@ -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 = emptyList(), - call: IrBuilderWithScope.(IrExpression, List) -> IrExpression, + root: Node, + variables: List = emptyList(), + call: IrBuilderWithScope.(IrExpression, List) -> 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 = emptyList(), - call: IrBuilderWithScope.(IrExpression?, List) -> IrExpression, + root: Node?, + variables: List = emptyList(), + call: IrBuilderWithScope.(IrExpression?, List) -> 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, - call: IrBuilderWithScope.(IrExpression, List) -> IrExpression, + node: Node, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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, - call: IrBuilderWithScope.(IrExpression, List) -> IrExpression, + node: ExpressionNode, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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, - call: IrBuilderWithScope.(IrExpression, List) -> IrExpression, + node: AndNode, + index: Int, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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, - call: IrBuilderWithScope.(IrExpression, List) -> IrExpression, + node: OrNode, + index: Int, + variables: List, + call: IrBuilderWithScope.(IrExpression, List) -> 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 + ) + } } - } } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/ExpressionTree.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/ExpressionTree.kt index 0767ff9fd78..8cb67d38232 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/ExpressionTree.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/ExpressionTree.kt @@ -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() - val children: List get() = _children + private val _children = mutableListOf() + val children: List 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() - val expressions: List = _expressions + private val _expressions = mutableListOf() + val expressions: List = _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 { - 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 { + 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() } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrDiagram.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrDiagram.kt index c0511a43ecd..9b780415405 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrDiagram.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrDiagram.kt @@ -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, ): 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 } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrTemporaryVariable.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrTemporaryVariable.kt index 678a6910bce..a4aa775cd35 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrTemporaryVariable.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/IrTemporaryVariable.kt @@ -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, + private val builder: IrStatementsBuilder<*>, + private val transform: Set, ) : IrElementTransformerVoid() { - private val _variables = mutableListOf() - val variables: List = _variables + private val _variables = mutableListOf() + val variables: List = _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) + } } - } } diff --git a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/SourceFile.kt b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/SourceFile.kt index 2b9b6f87bcb..3deeff2dd51 100644 --- a/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/SourceFile.kt +++ b/plugins/power-assert/power-assert.backend/src/org/jetbrains/kotlin/powerassert/diagram/SourceFile.kt @@ -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)!! + } } diff --git a/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCommandLineProcessor.kt b/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCommandLineProcessor.kt index 3249293f524..1b9295cf633 100644 --- a/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCommandLineProcessor.kt +++ b/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCommandLineProcessor.kt @@ -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 = 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 = 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}") + } } - } } diff --git a/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCompilerPluginRegistrar.kt b/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCompilerPluginRegistrar.kt index f8e2e6d7e03..86ea7ae2209 100644 --- a/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCompilerPluginRegistrar.kt +++ b/plugins/power-assert/power-assert.cli/src/org/jetbrains/kotlin/powerassert/PowerAssertCompilerPluginRegistrar.kt @@ -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>("fully-qualified function names") @AutoService(CompilerPluginRegistrar::class) class PowerAssertCompilerPluginRegistrar( - private val functions: Set, + private val functions: Set, ) : 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())) + } }