Prepare kotlin-power-assert files for git merge with Kotlin
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
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.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
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
fun IrBuilderWithScope.irString(builderAction: StringBuilder.() -> Unit) =
|
||||
irString(buildString { builderAction() })
|
||||
|
||||
fun IrBuilderWithScope.irLambda(
|
||||
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()
|
||||
}
|
||||
parent = scope.parent
|
||||
}
|
||||
return IrFunctionExpressionImpl(startOffset, endOffset, lambdaType, lambda, IrStatementOrigin.LAMBDA)
|
||||
}
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import com.bnorm.power.delegate.FunctionDelegate
|
||||
import com.bnorm.power.delegate.LambdaFunctionDelegate
|
||||
import com.bnorm.power.delegate.SamConversionLambdaFunctionDelegate
|
||||
import com.bnorm.power.delegate.SimpleFunctionDelegate
|
||||
import com.bnorm.power.diagram.IrTemporaryVariable
|
||||
import com.bnorm.power.diagram.Node
|
||||
import com.bnorm.power.diagram.SourceFile
|
||||
import com.bnorm.power.diagram.buildDiagramNesting
|
||||
import com.bnorm.power.diagram.buildDiagramNestingNullable
|
||||
import com.bnorm.power.diagram.buildTree
|
||||
import com.bnorm.power.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
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.parentClassId
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.asString
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.ir.builders.irString
|
||||
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.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.util.classId
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.isFileClass
|
||||
import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction
|
||||
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
|
||||
|
||||
class PowerAssertCallTransformer(
|
||||
private val sourceFile: SourceFile,
|
||||
private val context: IrPluginContext,
|
||||
private val messageCollector: MessageCollector,
|
||||
private val functions: Set<FqName>,
|
||||
) : IrElementTransformerVoidWithContext() {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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>,
|
||||
): 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 }
|
||||
}
|
||||
}
|
||||
// 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 isStringFunction(type: IrType): Boolean =
|
||||
type.isFunctionOrKFunction() && type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import com.bnorm.power.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
|
||||
|
||||
class PowerAssertIrGenerationExtension(
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.delegate
|
||||
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.ir.builders.parent
|
||||
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.IrExpression
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
|
||||
interface FunctionDelegate {
|
||||
val function: IrFunction
|
||||
val messageParameter: IrValueParameter
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.delegate
|
||||
|
||||
import com.bnorm.power.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
|
||||
|
||||
class LambdaFunctionDelegate(
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
) : FunctionDelegate {
|
||||
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)
|
||||
}
|
||||
irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = expression,
|
||||
)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.delegate
|
||||
|
||||
import com.bnorm.power.irLambda
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.irReturn
|
||||
import org.jetbrains.kotlin.ir.builders.irSamConversion
|
||||
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
|
||||
|
||||
class SamConversionLambdaFunctionDelegate(
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
) : FunctionDelegate {
|
||||
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)
|
||||
}
|
||||
val expression = irSamConversion(lambda, messageParameter.type)
|
||||
irCallCopy(
|
||||
overload = overload,
|
||||
original = original,
|
||||
dispatchReceiver = dispatchReceiver,
|
||||
extensionReceiver = extensionReceiver,
|
||||
valueArguments = valueArguments,
|
||||
messageArgument = expression,
|
||||
)
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.delegate
|
||||
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
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
|
||||
|
||||
class SimpleFunctionDelegate(
|
||||
private val overload: IrSimpleFunctionSymbol,
|
||||
override val messageParameter: IrValueParameter,
|
||||
) : FunctionDelegate {
|
||||
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,
|
||||
)
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.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.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
|
||||
fun IrBuilderWithScope.buildDiagramNesting(
|
||||
root: Node,
|
||||
variables: List<IrTemporaryVariable> = emptyList(),
|
||||
call: IrBuilderWithScope.(IrExpression, List<IrTemporaryVariable>) -> IrExpression,
|
||||
): IrExpression {
|
||||
return buildExpression(root, variables) { argument, subStack ->
|
||||
call(argument, subStack)
|
||||
}
|
||||
}
|
||||
|
||||
fun IrBuilderWithScope.buildDiagramNestingNullable(
|
||||
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)
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.buildExpression(
|
||||
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")
|
||||
}
|
||||
|
||||
/**
|
||||
* ```
|
||||
* val result = call(1 + 2 + 3)
|
||||
* ```
|
||||
* Transforms to
|
||||
* ```
|
||||
* val result = run {
|
||||
* val tmp0 = 1 + 2
|
||||
* val tmp1 = tmp0 + 3
|
||||
* call(tmp1, <diagram>)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun IrBuilderWithScope.add(
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ```
|
||||
* val result = call(1 == 1 && 2 == 2)
|
||||
* ```
|
||||
* Transforms to
|
||||
* ```
|
||||
* val result = run {
|
||||
* val tmp0 = 1 == 1
|
||||
* if (tmp0) {
|
||||
* val tmp1 = 2 == 2
|
||||
* call(tmp1, <diagram>)
|
||||
* }
|
||||
* else call(false, <diagram>)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun IrBuilderWithScope.nest(
|
||||
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 result = call(1 == 1 || 2 == 2)
|
||||
* ```
|
||||
* Transforms to
|
||||
* ```
|
||||
* val result = run {
|
||||
* val tmp0 = 1 == 1
|
||||
* if (tmp0) call(true, <diagram>)
|
||||
* else {
|
||||
* val tmp1 = 2 == 2
|
||||
* call(tmp1, <diagram>)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun IrBuilderWithScope.nest(
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.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.util.dumpKotlinLike
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
|
||||
abstract class Node {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AndNode : Node() {
|
||||
override fun toString() = "AndNode"
|
||||
}
|
||||
|
||||
class OrNode : Node() {
|
||||
override fun toString() = "OrNode"
|
||||
}
|
||||
|
||||
class ExpressionNode : Node() {
|
||||
private val _expressions = mutableListOf<IrExpression>()
|
||||
val expressions: List<IrExpression> = _expressions
|
||||
|
||||
fun add(expression: IrExpression) {
|
||||
_expressions.add(expression)
|
||||
}
|
||||
|
||||
override fun toString() = "ExpressionNode(${_expressions.map { it.dumpKotlinLike() }})"
|
||||
}
|
||||
|
||||
fun buildTree(expression: IrExpression): Node? {
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.diagram
|
||||
|
||||
import com.bnorm.power.irString
|
||||
import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.SourceRangeInfo
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
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
|
||||
|
||||
fun IrBuilderWithScope.irDiagramString(
|
||||
sourceFile: SourceFile,
|
||||
prefix: IrExpression? = null,
|
||||
call: IrCall,
|
||||
variables: List<IrTemporaryVariable>,
|
||||
): IrExpression {
|
||||
val callInfo = sourceFile.getSourceRangeInfo(call)
|
||||
val callIndent = callInfo.startColumnNumber
|
||||
|
||||
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")
|
||||
|
||||
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 }
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class ValueDisplay(
|
||||
val value: IrVariable,
|
||||
val indent: Int,
|
||||
val row: Int,
|
||||
val source: String,
|
||||
)
|
||||
|
||||
private fun IrTemporaryVariable.toValueDisplay(
|
||||
fileSource: SourceFile,
|
||||
callIndent: Int,
|
||||
originalInfo: SourceRangeInfo,
|
||||
): ValueDisplay {
|
||||
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 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for determining the diagram display offset of the expression
|
||||
* beginning from the startOffset of the expression.
|
||||
*
|
||||
* Equality:
|
||||
* ```
|
||||
* number == 42
|
||||
* | <- startOffset
|
||||
* | <- display offset: 7
|
||||
* ```
|
||||
*
|
||||
* Arithmetic:
|
||||
* ```
|
||||
* i + 2
|
||||
* | <- startOffset
|
||||
* | <- display offset: 2
|
||||
* ```
|
||||
*
|
||||
* Infix:
|
||||
* ```
|
||||
* 1 shl 2
|
||||
* | <- startOffset
|
||||
* | <- display offset: 2
|
||||
* ```
|
||||
*
|
||||
* Standard:
|
||||
* ```
|
||||
* 1.shl(2)
|
||||
* | <- startOffset
|
||||
* | <- display offset: 0
|
||||
* ```
|
||||
*/
|
||||
private fun findDisplayOffset(
|
||||
sourceFile: SourceFile,
|
||||
expression: IrExpression,
|
||||
source: String,
|
||||
): Int {
|
||||
return when (expression) {
|
||||
is IrMemberAccessExpression<*> -> memberAccessOffset(sourceFile, expression, source)
|
||||
is IrTypeOperatorCall -> typeOperatorOffset(expression, source)
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun memberAccessOffset(
|
||||
sourceFile: SourceFile,
|
||||
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
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun typeOperatorOffset(
|
||||
expression: IrTypeOperatorCall,
|
||||
source: String,
|
||||
): Int {
|
||||
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
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power.diagram
|
||||
|
||||
import org.jetbrains.kotlin.ir.builders.IrStatementsBuilder
|
||||
import org.jetbrains.kotlin.ir.builders.irGet
|
||||
import org.jetbrains.kotlin.ir.builders.irTemporary
|
||||
import org.jetbrains.kotlin.ir.declarations.IrVariable
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
|
||||
data class IrTemporaryVariable(
|
||||
val temporary: IrVariable,
|
||||
val original: IrExpression,
|
||||
)
|
||||
|
||||
class IrTemporaryExtractionTransformer(
|
||||
private val builder: IrStatementsBuilder<*>,
|
||||
private val transform: Set<IrExpression>,
|
||||
) : IrElementTransformerVoid() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.bnorm.power.diagram
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.SourceRangeInfo
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.path
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import java.io.File
|
||||
|
||||
data class SourceFile(
|
||||
private val irFile: IrFile,
|
||||
) {
|
||||
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
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
fun getCompilerMessageLocation(element: IrElement): CompilerMessageLocation {
|
||||
val info = getSourceRangeInfo(element)
|
||||
val lineContent = getText(info)
|
||||
return CompilerMessageLocation.create(irFile.path, info.startLineNumber, info.startColumnNumber, lineContent)!!
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOption
|
||||
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
|
||||
@AutoService(CommandLineProcessor::class)
|
||||
class PowerAssertCommandLineProcessor : CommandLineProcessor {
|
||||
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 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
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
|
||||
|
||||
val KEY_FUNCTIONS = CompilerConfigurationKey<List<String>>("fully-qualified function names")
|
||||
|
||||
@AutoService(CompilerPluginRegistrar::class)
|
||||
class PowerAssertCompilerPluginRegistrar(
|
||||
private val functions: Set<FqName>,
|
||||
) : CompilerPluginRegistrar() {
|
||||
@Suppress("unused")
|
||||
constructor() : this(emptySet()) // Used by service loader
|
||||
|
||||
override val supportsK2: Boolean = true
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ArithmeticExpressionTest {
|
||||
@Test
|
||||
fun `assertion with inline addition`() {
|
||||
val actual = executeMainAssertion("assert(1 + 1 == 4)")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(1 + 1 == 4)
|
||||
| |
|
||||
| false
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `assertion with inline subtraction`() {
|
||||
val actual = executeMainAssertion("assert(3 - 1 == 4)")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(3 - 1 == 4)
|
||||
| |
|
||||
| false
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `assertion with inline multiplication`() {
|
||||
val actual = executeMainAssertion("assert(1 * 2 == 4)")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(1 * 2 == 4)
|
||||
| |
|
||||
| false
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `assertion with inline division`() {
|
||||
val actual = executeMainAssertion("assert(2 / 1 == 4)")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(2 / 1 == 4)
|
||||
| |
|
||||
| false
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `assertion with inline prefix increment`() {
|
||||
val actual = executeMainAssertion(
|
||||
"""
|
||||
var i = 1
|
||||
assert(++i == 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(++i == 4)
|
||||
| |
|
||||
| false
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `assertion with inline postfix increment`() {
|
||||
val actual = executeMainAssertion(
|
||||
"""
|
||||
var i = 1
|
||||
assert(i++ == 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(i++ == 4)
|
||||
| |
|
||||
| false
|
||||
1
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `assertion with inline prefix decrement`() {
|
||||
val actual = executeMainAssertion(
|
||||
"""
|
||||
var i = 3
|
||||
assert(--i == 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(--i == 4)
|
||||
| |
|
||||
| false
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `assertion with inline postfix decrement`() {
|
||||
val actual = executeMainAssertion(
|
||||
"""
|
||||
var i = 3
|
||||
assert(i-- == 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(i-- == 4)
|
||||
| |
|
||||
| false
|
||||
3
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CastExpressionTest {
|
||||
@Test
|
||||
fun `instance check is correctly aligned`() {
|
||||
val actual = executeMainAssertion("""assert(null is String)""")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(null is String)
|
||||
|
|
||||
false
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `negative instance check is correctly aligned`() {
|
||||
val actual = executeMainAssertion("""assert("Hello, world!" !is String)""")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert("Hello, world!" !is String)
|
||||
|
|
||||
false
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `smart casts do not duplicate output`() {
|
||||
val actual = executeMainAssertion(
|
||||
"""
|
||||
val greeting: Any = "hello"
|
||||
assert(greeting is String && greeting.length == 2)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert(greeting is String && greeting.length == 2)
|
||||
| | | | |
|
||||
| | | | false
|
||||
| | | 5
|
||||
| | hello
|
||||
| true
|
||||
hello
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import com.tschuchort.compiletesting.KotlinCompilation
|
||||
import com.tschuchort.compiletesting.SourceFile
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.io.OutputStream
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
private val DEFAULT_COMPILER_PLUGIN_REGISTRARS = arrayOf(
|
||||
PowerAssertCompilerPluginRegistrar(setOf(FqName("kotlin.assert"))),
|
||||
)
|
||||
|
||||
fun compile(
|
||||
list: List<SourceFile>,
|
||||
vararg compilerPluginRegistrars: CompilerPluginRegistrar = DEFAULT_COMPILER_PLUGIN_REGISTRARS,
|
||||
): KotlinCompilation.Result {
|
||||
return KotlinCompilation().apply {
|
||||
sources = list
|
||||
messageOutputStream = object : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
// black hole all writes
|
||||
}
|
||||
|
||||
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
// black hole all writes
|
||||
}
|
||||
}
|
||||
this.compilerPluginRegistrars = compilerPluginRegistrars.toList()
|
||||
inheritClassPath = true
|
||||
}.compile()
|
||||
}
|
||||
|
||||
fun executeAssertion(
|
||||
@Language("kotlin") source: String,
|
||||
vararg compilerPluginRegistrars: CompilerPluginRegistrar = DEFAULT_COMPILER_PLUGIN_REGISTRARS,
|
||||
): String {
|
||||
val result = compile(
|
||||
listOf(SourceFile.kotlin("main.kt", source, trimIndent = false)),
|
||||
*compilerPluginRegistrars,
|
||||
)
|
||||
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode, result.messages)
|
||||
|
||||
val kClazz = result.classLoader.loadClass("MainKt")
|
||||
val main = kClazz.declaredMethods.single { it.name == "main" && it.parameterCount == 0 }
|
||||
try {
|
||||
try {
|
||||
main.invoke(null)
|
||||
} catch (t: InvocationTargetException) {
|
||||
throw t.cause!!
|
||||
}
|
||||
fail("should have thrown assertion")
|
||||
} catch (t: Throwable) {
|
||||
return t.message ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
fun executeMainAssertion(mainBody: String) = executeAssertion(
|
||||
"""
|
||||
fun main() {
|
||||
$mainBody
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
fun assertMessage(
|
||||
@Language("kotlin") source: String,
|
||||
message: String,
|
||||
vararg compilerPluginRegistrars: CompilerPluginRegistrar = DEFAULT_COMPILER_PLUGIN_REGISTRARS,
|
||||
) {
|
||||
val actual = executeAssertion(source, *compilerPluginRegistrars)
|
||||
assertEquals(message, actual)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import com.tschuchort.compiletesting.KotlinCompilation
|
||||
import com.tschuchort.compiletesting.SourceFile
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
class DebugFunctionTest {
|
||||
@Test
|
||||
fun `debug function transformation`() {
|
||||
val actual = executeMainDebug(
|
||||
"""
|
||||
dbg(1 + 2 + 3)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
dbg(1 + 2 + 3)
|
||||
| |
|
||||
| 6
|
||||
3
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `debug function transformation with message`() {
|
||||
val actual = executeMainDebug(
|
||||
"""
|
||||
dbg(1 + 2 + 3, "Message:")
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Message:
|
||||
dbg(1 + 2 + 3, "Message:")
|
||||
| |
|
||||
| 6
|
||||
3
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeMainDebug(mainBody: String): String {
|
||||
val file = SourceFile.kotlin(
|
||||
name = "main.kt",
|
||||
contents = """
|
||||
fun <T> dbg(value: T): T = value
|
||||
|
||||
fun <T> dbg(value: T, msg: String): T {
|
||||
throw RuntimeException("result:"+msg)
|
||||
return value
|
||||
}
|
||||
|
||||
fun main() {
|
||||
$mainBody
|
||||
}
|
||||
""",
|
||||
trimIndent = false,
|
||||
)
|
||||
|
||||
val result = compile(listOf(file), PowerAssertCompilerPluginRegistrar(setOf(FqName("dbg"))))
|
||||
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)
|
||||
|
||||
val kClazz = result.classLoader.loadClass("MainKt")
|
||||
val main = kClazz.declaredMethods.single { it.name == "main" && it.parameterCount == 0 }
|
||||
return getMainResult(main)
|
||||
}
|
||||
|
||||
fun getMainResult(main: Method): String {
|
||||
try {
|
||||
main.invoke(null)
|
||||
fail("main did not throw expected exception")
|
||||
} catch (t: InvocationTargetException) {
|
||||
with(t.cause) {
|
||||
if (this is RuntimeException && message != null && message!!.startsWith("result:")) {
|
||||
return message!!.substringAfter("result:")
|
||||
}
|
||||
}
|
||||
throw t.cause!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import com.tschuchort.compiletesting.KotlinCompilation
|
||||
import com.tschuchort.compiletesting.SourceFile
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
class InfixFunctionTest {
|
||||
@Test
|
||||
fun `extension infix function call includes receiver`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
(1 + 1) mustEqual (2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
(1 + 1) mustEqual (2 + 4)
|
||||
| |
|
||||
| 6
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension infix function call with constant receiver`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
1 mustEqual (2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
1 mustEqual (2 + 4)
|
||||
|
|
||||
6
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension infix function call with constant parameter`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
(1 + 1) mustEqual 6
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
(1 + 1) mustEqual 6
|
||||
|
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension infix function call with only constants`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
2 mustEqual 6
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension non-infix function call includes receiver`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
(1 + 1).mustEqual(2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
(1 + 1).mustEqual(2 + 4)
|
||||
| |
|
||||
| 6
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension non-infix function call with constant receiver`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
1.mustEqual(2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
1.mustEqual(2 + 4)
|
||||
|
|
||||
6
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension non-infix function call with constant parameter`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
(1 + 1).mustEqual(6)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
(1 + 1).mustEqual(6)
|
||||
|
|
||||
2
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension non-infix function call with only constants`() {
|
||||
val actual = runExtensionInfix(
|
||||
"""
|
||||
2.mustEqual(6)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch infix function call includes receiver`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(1 + 1) mustEqual (2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(1 + 1) mustEqual (2 + 4)
|
||||
| | |
|
||||
| | 6
|
||||
| 2
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch infix function call with constant receiver`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(1) mustEqual (2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(1) mustEqual (2 + 4)
|
||||
| |
|
||||
| 6
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch infix function call with constant parameter`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(1 + 1) mustEqual 6
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(1 + 1) mustEqual 6
|
||||
| |
|
||||
| 2
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch infix function call with only constants`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(2) mustEqual 6
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(2) mustEqual 6
|
||||
|
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch non-infix function call includes receiver`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(1 + 1).mustEqual(2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(1 + 1).mustEqual(2 + 4)
|
||||
| | |
|
||||
| | 6
|
||||
| 2
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch non-infix function call with constant receiver`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(1).mustEqual(2 + 4)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(1).mustEqual(2 + 4)
|
||||
| |
|
||||
| 6
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch non-infix function call with constant parameter`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(1 + 1).mustEqual(6)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(1 + 1).mustEqual(6)
|
||||
| |
|
||||
| 2
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatch non-infix function call with only constants`() {
|
||||
val actual = runDispatchInfix(
|
||||
"""
|
||||
Wrapper(2).mustEqual(6)
|
||||
""".trimIndent(),
|
||||
)
|
||||
assertEquals(
|
||||
"""
|
||||
Wrapper(2).mustEqual(6)
|
||||
|
|
||||
Wrapper
|
||||
""".trimIndent(),
|
||||
actual.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun runExtensionInfix(mainBody: String): String {
|
||||
return run(
|
||||
SourceFile.kotlin(
|
||||
name = "main.kt",
|
||||
contents = """
|
||||
infix fun <V> V.mustEqual(expected: V): Unit = assert(this == expected)
|
||||
|
||||
fun <V> V.mustEqual(expected: V, message: () -> String): Unit =
|
||||
assert(this == expected, message)
|
||||
|
||||
fun main() {
|
||||
$mainBody
|
||||
}
|
||||
""".trimIndent(),
|
||||
trimIndent = false,
|
||||
),
|
||||
setOf(FqName("mustEqual")),
|
||||
)
|
||||
}
|
||||
|
||||
private fun runDispatchInfix(mainBody: String): String {
|
||||
return run(
|
||||
SourceFile.kotlin(
|
||||
name = "main.kt",
|
||||
contents = """
|
||||
class Wrapper<V>(
|
||||
private val value: V
|
||||
) {
|
||||
infix fun mustEqual(expected: V): Unit = assert(value == expected)
|
||||
|
||||
fun mustEqual(expected: V, message: () -> String): Unit =
|
||||
assert(value == expected, message)
|
||||
|
||||
override fun toString() = "Wrapper"
|
||||
}
|
||||
|
||||
fun main() {
|
||||
$mainBody
|
||||
}
|
||||
""".trimIndent(),
|
||||
trimIndent = false,
|
||||
),
|
||||
setOf(FqName("Wrapper.mustEqual")),
|
||||
)
|
||||
}
|
||||
|
||||
private fun run(file: SourceFile, fqNames: Set<FqName>, main: String = "MainKt"): String {
|
||||
val result = compile(listOf(file), PowerAssertCompilerPluginRegistrar(fqNames))
|
||||
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode, "Failed with messages: " + result.messages)
|
||||
|
||||
val kClazz = result.classLoader.loadClass(main)
|
||||
val mainMethod = kClazz.declaredMethods.single { it.name == "main" && it.parameterCount == 0 }
|
||||
try {
|
||||
try {
|
||||
mainMethod.invoke(null)
|
||||
} catch (t: InvocationTargetException) {
|
||||
throw t.cause!!
|
||||
}
|
||||
fail("should have thrown assertion")
|
||||
} catch (t: Throwable) {
|
||||
return t.message ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import kotlin.test.Test
|
||||
|
||||
class ObjectLiteralTest {
|
||||
@Test
|
||||
fun `internals of object literal should not be separated`() {
|
||||
assertMessage(
|
||||
"""
|
||||
fun main() {
|
||||
assert(object { override fun toString() = "ANONYMOUS" }.toString() == "toString()")
|
||||
}""",
|
||||
"""
|
||||
Assertion failed
|
||||
assert(object { override fun toString() = "ANONYMOUS" }.toString() == "toString()")
|
||||
| | |
|
||||
| | false
|
||||
| ANONYMOUS
|
||||
ANONYMOUS
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class RegexMatchTest {
|
||||
@Test
|
||||
fun `regex matches`() {
|
||||
val actual = executeMainAssertion("""assert("Hello, World".matches("[A-Za-z]+".toRegex()))""")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert("Hello, World".matches("[A-Za-z]+".toRegex()))
|
||||
| |
|
||||
| [A-Za-z]+
|
||||
false
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `infix regex matches`() {
|
||||
val actual = executeMainAssertion("""assert("Hello, World" matches "[A-Za-z]+".toRegex())""")
|
||||
assertEquals(
|
||||
"""
|
||||
Assertion failed
|
||||
assert("Hello, World" matches "[A-Za-z]+".toRegex())
|
||||
| |
|
||||
| [A-Za-z]+
|
||||
false
|
||||
""".trimIndent(),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Brian Norman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import kotlin.test.Test
|
||||
|
||||
class VarargTest {
|
||||
@Test
|
||||
fun `implicit array of vararg parameters is excluded from diagram`() {
|
||||
assertMessage(
|
||||
"""
|
||||
fun main() {
|
||||
var i = 0
|
||||
assert(listOf("a", "b", "c") == listOf(i++, i++, i++))
|
||||
}""",
|
||||
"""
|
||||
Assertion failed
|
||||
assert(listOf("a", "b", "c") == listOf(i++, i++, i++))
|
||||
| | | | | |
|
||||
| | | | | 2
|
||||
| | | | 1
|
||||
| | | 0
|
||||
| | [0, 1, 2]
|
||||
| false
|
||||
[a, b, c]
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user