From d81b12e5a4fd9e4eb1ed19f51961c5f76ef91370 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Fri, 7 Oct 2022 16:29:39 +0300 Subject: [PATCH 1/6] Add support for Kotlin top-level functions --- .../src/main/kotlin/org/utbot/common/KClassUtil.kt | 3 +++ .../org/utbot/framework/process/EngineMain.kt | 9 +++++---- .../plugin/ui/actions/GenerateTestsAction.kt | 8 +++++++- .../plugin/ui/utils/KotlinPsiElementHandler.kt | 13 +++++++++++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt index 1c0a88d02a..aa29fa1090 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt @@ -14,3 +14,6 @@ fun Method.invokeCatching(obj: Any?, args: List) = try { val KClass<*>.allNestedClasses: List> get() = listOf(this) + nestedClasses.flatMap { it.allNestedClasses } + +val Class<*>.allNestedClasses: List> + get() = listOf(this) + this.declaredClasses.flatMap { it.declaredClasses.toList() } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt index 2498c729e0..9d0c753c5f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt @@ -21,6 +21,7 @@ import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method import org.utbot.framework.plugin.services.JdkInfo import org.utbot.framework.process.generated.* import org.utbot.framework.util.ConflictTriggers @@ -36,7 +37,7 @@ import org.utbot.summary.summarize import java.io.File import java.net.URLClassLoader import java.nio.file.Paths -import kotlin.reflect.full.functions +import kotlin.reflect.jvm.kotlinFunction import kotlin.time.Duration.Companion.seconds private val messageFromMainTimeoutMillis = 120.seconds @@ -158,8 +159,8 @@ private fun EngineProcessModel.setup( synchronizer.measureExecutionForTermination(findMethodsInClassMatchingSelected) { params -> val classId = kryoHelper.readObject(params.classId) val selectedSignatures = params.signatures.map { Signature(it.name, it.parametersTypes) } - FindMethodsInClassMatchingSelectedResult(kryoHelper.writeObject(classId.jClass.kotlin.allNestedClasses.flatMap { clazz -> - clazz.functions.sortedWith(compareBy { selectedSignatures.indexOf(it.signature()) }) + FindMethodsInClassMatchingSelectedResult(kryoHelper.writeObject(classId.jClass.allNestedClasses.flatMap { clazz -> + clazz.id.allMethods.mapNotNull { it.method.kotlinFunction }.sortedWith(compareBy { selectedSignatures.indexOf(it.signature()) }) .filter { it.signature().normalized() in selectedSignatures } .map { it.executableId } })) @@ -168,7 +169,7 @@ private fun EngineProcessModel.setup( val classId = kryoHelper.readObject(params.classId) val bySignature = kryoHelper.readObject>>(params.bySignature) FindMethodParamNamesResult(kryoHelper.writeObject( - classId.jClass.kotlin.allNestedClasses.flatMap { it.functions } + classId.jClass.allNestedClasses.flatMap { clazz -> clazz.id.allMethods.mapNotNull { it.method.kotlinFunction } } .mapNotNull { method -> bySignature[method.signature()]?.let { params -> method.executableId to params } } .toMap() )) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt index 3060565e87..01d91878c8 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt @@ -16,8 +16,10 @@ import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.* +import com.intellij.psi.impl.PsiJavaParserFacadeImpl import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.util.classMembers.MemberInfo +import org.jetbrains.kotlin.asJava.findFacadeClass import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.util.toPsiDirectory import org.jetbrains.kotlin.idea.core.util.toPsiFile @@ -26,6 +28,7 @@ import org.utbot.intellij.plugin.util.extractFirstLevelMembers import org.utbot.intellij.plugin.util.isVisible import java.util.* import org.jetbrains.kotlin.j2k.getContainingClass +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.utils.addIfNotNull import org.utbot.framework.plugin.api.util.LockFile import org.utbot.intellij.plugin.models.packageName @@ -60,11 +63,13 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { if (editor != null) { //The action is being called from editor val file = e.getData(CommonDataKeys.PSI_FILE) ?: return null + //file.setName("abcdef") val element = findPsiElement(file, editor) ?: return null val psiElementHandler = PsiElementHandler.makePsiElementHandler(file) - if (psiElementHandler.isCreateTestActionAvailable(element)) { + // When in Kotlin file, we should propose top-level functions for testing + if (psiElementHandler.isCreateTestActionAvailable(element) || file is KtFile) { val srcClass = psiElementHandler.containingClass(element) ?: return null val srcSourceRoot = srcClass.getSourceRoot() ?: return null val srcMembers = srcClass.extractFirstLevelMembers(false) @@ -232,6 +237,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { return emptySet() } val allClasses = psiFiles.flatMap { getClassesFromFile(it) }.toMutableSet() + allClasses.addAll(psiFiles.mapNotNull { (it as? KtFile)?.findFacadeClass() }) for (psiDir in psiDirectories) allClasses += getAllClasses(psiDir) return allClasses diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt index 0beca8f9a1..a17608265f 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt @@ -2,6 +2,8 @@ package org.utbot.intellij.plugin.ui.utils import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement +import com.intellij.psi.util.findParentOfType +import org.jetbrains.kotlin.asJava.findFacadeClass import org.jetbrains.kotlin.idea.testIntegration.KotlinCreateTestIntention import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject @@ -9,6 +11,7 @@ import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.toKtPsiSourceElement import org.jetbrains.uast.toUElement class KotlinPsiElementHandler( @@ -31,6 +34,12 @@ class KotlinPsiElementHandler( element?.parentsWithSelf ?.firstOrNull { it is KtClassOrObject || it is KtNamedDeclaration && it.parent is KtFile } as? KtNamedDeclaration - override fun containingClass(element: PsiElement): PsiClass? = - element.parentsWithSelf.firstOrNull { it is KtClassOrObject }?.let { toPsi(it, PsiClass::class.java) } + override fun containingClass(element: PsiElement): PsiClass? { + element.findParentOfType(strict=false)?.let { + return toPsi(it, PsiClass::class.java) + } + return element.findParentOfType(strict=false)?.findFacadeClass()?.let { + toPsi(it, PsiClass::class.java) + } + } } \ No newline at end of file From 58502393f9f1720cb1e6bbdc74344ffdafe8deb4 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Mon, 10 Oct 2022 20:35:26 +0300 Subject: [PATCH 2/6] Corrected Kotlin codegen to properly handle top-level functions --- .../src/main/kotlin/org/utbot/common/KClassUtil.kt | 6 +----- .../org/utbot/framework/plugin/api/util/IdUtil.kt | 9 +++++++++ .../codegen/model/visitor/CgAbstractRenderer.kt | 10 +++++++--- .../framework/codegen/model/visitor/CgJavaRenderer.kt | 3 +++ .../codegen/model/visitor/CgKotlinRenderer.kt | 4 ++++ .../intellij/plugin/ui/actions/GenerateTestsAction.kt | 2 -- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt index aa29fa1090..8520b014a2 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt @@ -2,7 +2,6 @@ package org.utbot.common import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method -import kotlin.reflect.KClass val Class<*>.nameOfPackage: String get() = `package`?.name?:"" @@ -12,8 +11,5 @@ fun Method.invokeCatching(obj: Any?, args: List) = try { Result.failure(e.targetException) } -val KClass<*>.allNestedClasses: List> - get() = listOf(this) + nestedClasses.flatMap { it.allNestedClasses } - val Class<*>.allNestedClasses: List> - get() = listOf(this) + this.declaredClasses.flatMap { it.declaredClasses.toList() } \ No newline at end of file + get() = listOf(this) + this.declaredClasses.flatMap { it.allNestedClasses.toList() } \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 62e077cfdc..15e0c9e483 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -25,6 +25,7 @@ import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KProperty import kotlin.reflect.full.instanceParameter +import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaGetter @@ -178,6 +179,14 @@ val ClassId.isDoubleType: Boolean val ClassId.isClassType: Boolean get() = this == classClassId +/** + * Checks if the class is a Kotlin class with kind File (see [Metadata.kind] for more details) + */ +val ClassId.isKotlinFile: Boolean + get() = jClass.annotations.filterIsInstance().singleOrNull()?.let { + KotlinClassHeader.Kind.getById(it.kind) == KotlinClassHeader.Kind.FILE_FACADE + } ?: false + val voidClassId = ClassId("void") val booleanClassId = ClassId("boolean") val byteClassId = ClassId("byte") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index b4d7a3c8d2..e402199dd2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -131,8 +131,10 @@ internal abstract class CgAbstractRenderer( } } + protected abstract val ClassId.shouldBeOmittedWhenUsedAsCaller: Boolean + private val MethodId.accessibleByName: Boolean - get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.generatedClass + get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.generatedClass || classId.shouldBeOmittedWhenUsedAsCaller override fun visit(element: CgElement) { val error = @@ -654,8 +656,10 @@ internal abstract class CgAbstractRenderer( } override fun visit(element: CgStaticFieldAccess) { - print(element.declaringClass.asString()) - print(".") + if (!element.declaringClass.shouldBeOmittedWhenUsedAsCaller) { + print(element.declaringClass.asString()) + print(".") + } print(element.fieldName) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt index 88bbba9ffa..a7eaf699e5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt @@ -60,6 +60,9 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C override val langPackage: String = "java.lang" + override val ClassId.shouldBeOmittedWhenUsedAsCaller: Boolean + get() = false + override fun visit(element: AbstractCgClass<*>) { for (annotation in element.annotations) { annotation.accept(this) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index 09459b5f8b..a2730b41c2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -53,6 +53,7 @@ import org.utbot.framework.plugin.api.TypeParameters import org.utbot.framework.plugin.api.WildcardTypeParameter import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isKotlinFile import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.isPrimitiveWrapper import org.utbot.framework.plugin.api.util.kClass @@ -72,6 +73,9 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = override val langPackage: String = "kotlin" + override val ClassId.shouldBeOmittedWhenUsedAsCaller: Boolean + get() = isKotlinFile + override fun visit(element: AbstractCgClass<*>) { for (annotation in element.annotations) { annotation.accept(this) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt index 01d91878c8..abb8cb7501 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt @@ -16,7 +16,6 @@ import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.* -import com.intellij.psi.impl.PsiJavaParserFacadeImpl import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.util.classMembers.MemberInfo import org.jetbrains.kotlin.asJava.findFacadeClass @@ -63,7 +62,6 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { if (editor != null) { //The action is being called from editor val file = e.getData(CommonDataKeys.PSI_FILE) ?: return null - //file.setName("abcdef") val element = findPsiElement(file, editor) ?: return null val psiElementHandler = PsiElementHandler.makePsiElementHandler(file) From 81880d61969eea4303811fd27928c9241a15e770 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Fri, 14 Oct 2022 14:51:31 +0300 Subject: [PATCH 3/6] Fix codegen for kotlin extension functions --- .../utbot/framework/plugin/api/util/IdUtil.kt | 5 ++++ .../tree/CgCallableAccessManager.kt | 27 ++++++++++++++++++- .../model/visitor/CgAbstractRenderer.kt | 3 +++ .../framework/plugin/api/SignatureUtil.kt | 3 ++- .../intellij/plugin/util/SignaturesHelper.kt | 1 + 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 15e0c9e483..e965b0304b 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -24,12 +24,14 @@ import kotlin.reflect.KCallable import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KProperty +import kotlin.reflect.full.extensionReceiverParameter import kotlin.reflect.full.instanceParameter import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.javaMethod +import kotlin.reflect.jvm.kotlinFunction // ClassId utils @@ -439,6 +441,9 @@ val MethodId.method: Method ?: error("Can't find method $signature in ${declaringClass.name}") } +val MethodId.extensionReceiverParameterIndex: Int? + get() = this.method.kotlinFunction?.extensionReceiverParameter?.index + // TODO: maybe cache it somehow in the future val ConstructorId.constructor: Constructor<*> get() { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index 2c73f18281..75a0fdea5c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -49,6 +49,8 @@ import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.exceptions +import org.utbot.framework.plugin.api.util.extensionReceiverParameterIndex +import org.utbot.framework.plugin.api.util.humanReadableName import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isArray import org.utbot.framework.plugin.api.util.isSubtypeOf @@ -110,7 +112,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA override operator fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall { val resolvedArgs = args.resolve() val methodCall = if (method.canBeCalledWith(caller, resolvedArgs)) { - CgMethodCall(caller, method, resolvedArgs.guardedForDirectCallOf(method)) + CgMethodCall(caller, method, resolvedArgs.guardedForDirectCallOf(method)).takeCallerFromArgumentsIfNeeded() } else { method.callWithReflection(caller, resolvedArgs) } @@ -194,6 +196,29 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA else -> false } + /** + * For Kotlin extension functions, real caller is one of the arguments in JVM method (and declaration class is omitted), + * thus we should move it from arguments to caller + * + * For example, if we have `Int.f(a: Int)` declared in `Main.kt`, the JVM method signature will be `MainKt.f(Int, Int)` + * and in Kotlin we should render this not like `MainKt.f(a, b)` but like `a.f(b)` + */ + private fun CgMethodCall.takeCallerFromArgumentsIfNeeded(): CgMethodCall { + if (codegenLanguage == CodegenLanguage.KOTLIN) { + // TODO: reflection calls for util and some of mockito methods produce exceptions => currently runCatching is needed + // (but their reflection may be supported, alternatively maybe get rid of reflection somehow here) + runCatching { + executableId.extensionReceiverParameterIndex?.let { receiverIndex -> + require(caller == null) { "${executableId.humanReadableName} is an extension function but it already has a non-static caller provided" } + val args = arguments.toMutableList() + return CgMethodCall(args.removeAt(receiverIndex), executableId, args, typeParameters) + } + } + } + + return this + } + private infix fun CgExpression.canBeArgOf(type: ClassId): Boolean { // TODO: SAT-1210 support generics so that we wouldn't need to check specific cases such as this one if (this is CgExecutableCall && (executableId == any || executableId == anyOfClass)) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index e402199dd2..177604d4c3 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -711,7 +711,10 @@ internal abstract class CgAbstractRenderer( if (caller != null) { // 'this' can be omitted, otherwise render caller if (caller !is CgThisInstance) { + // TODO: we need parentheses for calls like (-1).inv(), do something smarter here + if (caller !is CgVariable) print("(") caller.accept(this) + if (caller !is CgVariable) print(")") renderAccess(caller) } } else { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SignatureUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SignatureUtil.kt index d8825e22e4..437da7add4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SignatureUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SignatureUtil.kt @@ -4,8 +4,9 @@ import kotlin.reflect.KFunction import kotlin.reflect.KParameter import kotlin.reflect.jvm.javaType +// Note that rules for obtaining signature here should correlate with PsiMethod.signature() fun KFunction<*>.signature() = - Signature(this.name, this.parameters.filter { it.kind == KParameter.Kind.VALUE }.map { it.type.javaType.typeName }) + Signature(this.name, this.parameters.filter { it.kind != KParameter.Kind.INSTANCE }.map { it.type.javaType.typeName }) data class Signature(val name: String, val parameterTypes: List) { diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SignaturesHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SignaturesHelper.kt index 8cf49a8af5..f3e9d07f39 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SignaturesHelper.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SignaturesHelper.kt @@ -7,6 +7,7 @@ import org.utbot.framework.plugin.api.Signature fun MemberInfo.signature(): Signature = (this.member as PsiMethod).signature() +// Note that rules for obtaining signature here should correlate with KFunction<*>.signature() private fun PsiMethod.signature() = Signature(this.name, this.parameterList.parameters.map { it.type.canonicalText From 00372ae817340ca1d65e545ecdbc7110897c6568 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Tue, 18 Oct 2022 20:37:56 +0300 Subject: [PATCH 4/6] Added tests for Kotlin top-level functions --- .../utbot/framework/plugin/api/util/IdUtil.kt | 1 + .../codegen/FileWithTopLevelFunctionsTest.kt | 38 +++++++++++++++++++ .../intellij/plugin/process/EngineProcess.kt | 11 +++++- ...ileWithTopLevelFunctionsReflectHelper.java | 6 +++ .../codegen/FileWithTopLevelFunctions.kt | 23 +++++++++++ 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt create mode 100644 utbot-sample/src/main/java/org/utbot/examples/codegen/FileWithTopLevelFunctionsReflectHelper.java create mode 100644 utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index e965b0304b..4e5da6f776 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -498,6 +498,7 @@ val Method.displayName: String val KCallable<*>.declaringClazz: Class<*> get() = when (this) { + is KFunction<*> -> javaMethod?.declaringClass?.kotlin is CallableReference -> owner as? KClass<*> else -> instanceParameter?.type?.classifier as? KClass<*> }?.java ?: tryConstructor(this) ?: error("Can't get parent class for $this") diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt new file mode 100644 index 0000000000..42b51995c2 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt @@ -0,0 +1,38 @@ +package org.utbot.examples.codegen + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.tests.infrastructure.UtValueTestCaseChecker + +internal class FileWithTopLevelFunctionsTest : UtValueTestCaseChecker(testClass = FileWithTopLevelFunctionsReflectHelper.clazz.kotlin) { + @Test + fun topLevelSumTest() { + check( + ::topLevelSum, + eq(1), + ) + } + + @Test + fun extensionOnBasicTypeTest() { + check( + Int::extensionOnBasicType, + eq(1), + ) + } + + @Test + fun extensionOnCustomClassTest() { + check( + CustomClass::extensionOnCustomClass, + eq(3), + additionalDependencies = dependenciesForClassExtensions + ) + } + + companion object { + // Compilation of extension methods for ref objects produces call to + // `kotlin.jvm.internal.Intrinsics::checkNotNullParameter`, so we need to add it to dependencies + val dependenciesForClassExtensions = arrayOf>(kotlin.jvm.internal.Intrinsics::class.java) + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt index 2a5fef497d..73bc6382f8 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt @@ -238,7 +238,16 @@ class EngineProcess(parent: Lifetime, val project: Project) { } private fun MemberInfo.paramNames(): List = - (this.member as PsiMethod).parameterList.parameters.map { it.name } + (this.member as PsiMethod).parameterList.parameters.map { + if (it.name.startsWith("\$this")) + // If member is Kotlin extension function, name of first argument isn't good for further usage, + // so we better choose name based on type of receiver. + // + // There seems no API to check whether parameter is an extension receiver by PSI + it.type.presentableText + else + it.name + } fun generate( mockInstalled: Boolean, diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/FileWithTopLevelFunctionsReflectHelper.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/FileWithTopLevelFunctionsReflectHelper.java new file mode 100644 index 0000000000..d5803f72bc --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/FileWithTopLevelFunctionsReflectHelper.java @@ -0,0 +1,6 @@ +package org.utbot.examples.codegen; + +// We can't access FileWithTopLevelFunctionsKt::class from Kotlin, so we use this class to get reflection from Java +public class FileWithTopLevelFunctionsReflectHelper { + static Class clazz = FileWithTopLevelFunctionsKt.class; +} diff --git a/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt b/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt new file mode 100644 index 0000000000..7f9b55aa83 --- /dev/null +++ b/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt @@ -0,0 +1,23 @@ +package org.utbot.examples.codegen + +// TODO: currently we can't properly handle properties in constructors, change CustomClass to data class after that is fixed +class CustomClass { + var x: Int = 0 + var y: Int = 0 + + fun f(): Int { + return 0 + } +} + +fun topLevelSum(a: Int, b: Int): Int { + return a + b +} + +fun Int.extensionOnBasicType(other: Int): Int { + return this + other +} + +fun CustomClass.extensionOnCustomClass(other: CustomClass): Boolean { + return x >= other.x && y >= other.y +} \ No newline at end of file From a3538dabe0a668bce4d45931b442b02c7ac830d0 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Mon, 24 Oct 2022 00:34:25 +0300 Subject: [PATCH 5/6] Fix some issues found by review --- .../src/main/kotlin/org/utbot/common/KClassUtil.kt | 7 +------ .../main/kotlin/org/utbot/common/ReflectionUtil.kt | 8 +++++++- .../org/utbot/framework/plugin/api/util/IdUtil.kt | 3 +++ .../constructor/tree/CgCallableAccessManager.kt | 6 +++--- .../codegen/model/visitor/CgAbstractRenderer.kt | 9 ++++++--- .../codegen/model/visitor/CgJavaRenderer.kt | 4 ++-- .../codegen/model/visitor/CgKotlinRenderer.kt | 4 ++-- .../plugin/ui/actions/GenerateTestsAction.kt | 13 +++---------- .../plugin/ui/utils/KotlinPsiElementHandler.kt | 14 +++++++++++--- .../intellij/plugin/ui/utils/PsiElementHandler.kt | 9 +++++++++ 10 files changed, 47 insertions(+), 30 deletions(-) diff --git a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt index 8520b014a2..84c4f1d5aa 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt @@ -3,13 +3,8 @@ package org.utbot.common import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method -val Class<*>.nameOfPackage: String get() = `package`?.name?:"" - fun Method.invokeCatching(obj: Any?, args: List) = try { Result.success(invoke(obj, *args.toTypedArray())) } catch (e: InvocationTargetException) { Result.failure(e.targetException) -} - -val Class<*>.allNestedClasses: List> - get() = listOf(this) + this.declaredClasses.flatMap { it.allNestedClasses.toList() } \ No newline at end of file +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt index c0e92d97ef..0b55259a9b 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt @@ -111,4 +111,10 @@ val Class<*>.isFinal get() = Modifier.isFinal(modifiers) val Class<*>.isProtected - get() = Modifier.isProtected(modifiers) \ No newline at end of file + get() = Modifier.isProtected(modifiers) + +val Class<*>.nameOfPackage: String + get() = `package`?.name?:"" + +val Class<*>.allNestedClasses: List> + get() = listOf(this) + this.declaredClasses.flatMap { it.allNestedClasses } \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 4e5da6f776..51641d8506 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -441,6 +441,9 @@ val MethodId.method: Method ?: error("Can't find method $signature in ${declaringClass.name}") } +/** + * See [KCallable.extensionReceiverParameter] for more details + */ val MethodId.extensionReceiverParameterIndex: Int? get() = this.method.kotlinFunction?.extensionReceiverParameter?.index diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index 75a0fdea5c..fd8e334f06 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -205,9 +205,9 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA */ private fun CgMethodCall.takeCallerFromArgumentsIfNeeded(): CgMethodCall { if (codegenLanguage == CodegenLanguage.KOTLIN) { - // TODO: reflection calls for util and some of mockito methods produce exceptions => currently runCatching is needed - // (but their reflection may be supported, alternatively maybe get rid of reflection somehow here) - runCatching { + // TODO: reflection calls for util and some of mockito methods produce exceptions => here we suppose that + // methods for BuiltinClasses are not extensions by default (which should be true as long as we suppose them to be java methods) + if (executableId.classId !is BuiltinClassId) { executableId.extensionReceiverParameterIndex?.let { receiverIndex -> require(caller == null) { "${executableId.humanReadableName} is an extension function but it already has a non-static caller provided" } val args = arguments.toMutableList() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index 177604d4c3..e2f3aef97c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -131,10 +131,13 @@ internal abstract class CgAbstractRenderer( } } - protected abstract val ClassId.shouldBeOmittedWhenUsedAsCaller: Boolean + /** + * Returns true if one can call methods of this class without specifying a caller (for example if ClassId represents this instance) + */ + protected abstract val ClassId.methodsAreAccessibleAsTopLevel: Boolean private val MethodId.accessibleByName: Boolean - get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.generatedClass || classId.shouldBeOmittedWhenUsedAsCaller + get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId.methodsAreAccessibleAsTopLevel override fun visit(element: CgElement) { val error = @@ -656,7 +659,7 @@ internal abstract class CgAbstractRenderer( } override fun visit(element: CgStaticFieldAccess) { - if (!element.declaringClass.shouldBeOmittedWhenUsedAsCaller) { + if (!element.declaringClass.methodsAreAccessibleAsTopLevel) { print(element.declaringClass.asString()) print(".") } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt index a7eaf699e5..8581409160 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt @@ -60,8 +60,8 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C override val langPackage: String = "java.lang" - override val ClassId.shouldBeOmittedWhenUsedAsCaller: Boolean - get() = false + override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + get() = this == context.generatedClass override fun visit(element: AbstractCgClass<*>) { for (annotation in element.annotations) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index a2730b41c2..63c5bbb258 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -73,8 +73,8 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = override val langPackage: String = "kotlin" - override val ClassId.shouldBeOmittedWhenUsedAsCaller: Boolean - get() = isKotlinFile + override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + get() = (this == context.generatedClass) || isKotlinFile override fun visit(element: AbstractCgClass<*>) { for (annotation in element.annotations) { diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt index abb8cb7501..aa764a5328 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt @@ -66,8 +66,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { val psiElementHandler = PsiElementHandler.makePsiElementHandler(file) - // When in Kotlin file, we should propose top-level functions for testing - if (psiElementHandler.isCreateTestActionAvailable(element) || file is KtFile) { + if (psiElementHandler.isCreateTestActionAvailable(element)) { val srcClass = psiElementHandler.containingClass(element) ?: return null val srcSourceRoot = srcClass.getSourceRoot() ?: return null val srcMembers = srcClass.extractFirstLevelMembers(false) @@ -221,7 +220,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { } private fun getAllClasses(directory: PsiDirectory): Set { - val allClasses = directory.files.flatMap { getClassesFromFile(it) }.toMutableSet() + val allClasses = directory.files.flatMap { PsiElementHandler.makePsiElementHandler(it).getClassesFromFile(it) }.toMutableSet() for (subDir in directory.subdirectories) allClasses += getAllClasses(subDir) return allClasses } @@ -234,16 +233,10 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { if (!dirsArePackages) { return emptySet() } - val allClasses = psiFiles.flatMap { getClassesFromFile(it) }.toMutableSet() + val allClasses = psiFiles.flatMap { PsiElementHandler.makePsiElementHandler(it).getClassesFromFile(it) }.toMutableSet() allClasses.addAll(psiFiles.mapNotNull { (it as? KtFile)?.findFacadeClass() }) for (psiDir in psiDirectories) allClasses += getAllClasses(psiDir) return allClasses } - - private fun getClassesFromFile(psiFile: PsiFile): List { - val psiElementHandler = PsiElementHandler.makePsiElementHandler(psiFile) - return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, psiElementHandler.classClass) - .map { psiElementHandler.toPsi(it, PsiClass::class.java) } - } } \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt index a17608265f..1ea7d3e110 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt @@ -2,6 +2,7 @@ package org.utbot.intellij.plugin.ui.utils import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.psi.util.findParentOfType import org.jetbrains.kotlin.asJava.findFacadeClass import org.jetbrains.kotlin.idea.testIntegration.KotlinCreateTestIntention @@ -11,7 +12,6 @@ import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf -import org.jetbrains.kotlin.toKtPsiSourceElement import org.jetbrains.uast.toUElement class KotlinPsiElementHandler( @@ -27,8 +27,16 @@ class KotlinPsiElementHandler( return element.toUElement()?.javaPsi as? T ?: error("Could not cast $element to $clazz") } - override fun isCreateTestActionAvailable(element: PsiElement): Boolean = - getTarget(element)?.let { KotlinCreateTestIntention().applicabilityRange(it) != null } ?: false + override fun getClassesFromFile(psiFile: PsiFile): List { + return listOfNotNull((psiFile as? KtFile)?.findFacadeClass()) + super.getClassesFromFile(psiFile) + } + + override fun isCreateTestActionAvailable(element: PsiElement): Boolean { + getTarget(element)?.let { + return KotlinCreateTestIntention().applicabilityRange(it) != null + } + return (element.containingFile as? KtFile)?.findFacadeClass() != null + } private fun getTarget(element: PsiElement?): KtNamedDeclaration? = element?.parentsWithSelf diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt index 6455cbbf55..eecfda5945 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt @@ -3,6 +3,7 @@ package org.utbot.intellij.plugin.ui.utils import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.psi.KtFile /** @@ -36,6 +37,14 @@ interface PsiElementHandler { */ fun toPsi(element: PsiElement, clazz: Class): T + /** + * Returns all classes that are declared in the [psiFile] + */ + fun getClassesFromFile(psiFile: PsiFile): List { + return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, classClass) + .map { toPsi(it, PsiClass::class.java) } + } + /** * Get java class of the Class in the corresponding syntax tree (PsiClass, KtClass, e.t.c). */ From cfe5932e1c99e048385b5fee9c804fb630d5dc44 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Tue, 25 Oct 2022 17:20:37 +0300 Subject: [PATCH 6/6] Improved tests and some comments added --- .../codegen/FileWithTopLevelFunctionsTest.kt | 9 +++++++-- .../codegen/model/visitor/CgKotlinRenderer.kt | 1 + .../examples/codegen/FileWithTopLevelFunctions.kt | 12 ++---------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt index 42b51995c2..307f143d91 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt @@ -3,7 +3,9 @@ package org.utbot.examples.codegen import org.junit.jupiter.api.Test import org.utbot.testcheckers.eq import org.utbot.tests.infrastructure.UtValueTestCaseChecker +import kotlin.reflect.KFunction3 +@Suppress("UNCHECKED_CAST") internal class FileWithTopLevelFunctionsTest : UtValueTestCaseChecker(testClass = FileWithTopLevelFunctionsReflectHelper.clazz.kotlin) { @Test fun topLevelSumTest() { @@ -24,8 +26,11 @@ internal class FileWithTopLevelFunctionsTest : UtValueTestCaseChecker(testClass @Test fun extensionOnCustomClassTest() { check( - CustomClass::extensionOnCustomClass, - eq(3), + // NB: cast is important here because we need to treat receiver as an argument to be able to check its content in matchers + CustomClass::extensionOnCustomClass as KFunction3<*, CustomClass, CustomClass, Boolean>, + eq(2), + { receiver, argument, result -> receiver === argument && result == true }, + { receiver, argument, result -> receiver !== argument && result == false }, additionalDependencies = dependenciesForClassExtensions ) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index 63c5bbb258..998010d91c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -74,6 +74,7 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = override val langPackage: String = "kotlin" override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + // NB: the order of operands is important as `isKotlinFile` uses reflection and thus can't be called on context.generatedClass get() = (this == context.generatedClass) || isKotlinFile override fun visit(element: AbstractCgClass<*>) { diff --git a/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt b/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt index 7f9b55aa83..f9b281a5ce 100644 --- a/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt +++ b/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt @@ -1,14 +1,6 @@ package org.utbot.examples.codegen -// TODO: currently we can't properly handle properties in constructors, change CustomClass to data class after that is fixed -class CustomClass { - var x: Int = 0 - var y: Int = 0 - - fun f(): Int { - return 0 - } -} +class CustomClass fun topLevelSum(a: Int, b: Int): Int { return a + b @@ -19,5 +11,5 @@ fun Int.extensionOnBasicType(other: Int): Int { } fun CustomClass.extensionOnCustomClass(other: CustomClass): Boolean { - return x >= other.x && y >= other.y + return this === other } \ No newline at end of file