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..84c4f1d5aa 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt @@ -2,15 +2,9 @@ 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?:"" fun Method.invokeCatching(obj: Any?, args: List) = try { Result.success(invoke(obj, *args.toTypedArray())) } catch (e: InvocationTargetException) { Result.failure(e.targetException) -} - -val KClass<*>.allNestedClasses: List> - get() = listOf(this) + nestedClasses.flatMap { it.allNestedClasses } +} \ 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 62e077cfdc..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 @@ -24,11 +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 @@ -178,6 +181,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") @@ -430,6 +441,12 @@ 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 + // TODO: maybe cache it somehow in the future val ConstructorId.constructor: Constructor<*> get() { @@ -484,6 +501,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..307f143d91 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt @@ -0,0 +1,43 @@ +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() { + check( + ::topLevelSum, + eq(1), + ) + } + + @Test + fun extensionOnBasicTypeTest() { + check( + Int::extensionOnBasicType, + eq(1), + ) + } + + @Test + fun extensionOnCustomClassTest() { + check( + // 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 + ) + } + + 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-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..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 @@ -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 => 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() + 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 b4d7a3c8d2..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,8 +131,13 @@ internal abstract class CgAbstractRenderer( } } + /** + * 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 + get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId.methodsAreAccessibleAsTopLevel override fun visit(element: CgElement) { val error = @@ -654,8 +659,10 @@ internal abstract class CgAbstractRenderer( } override fun visit(element: CgStaticFieldAccess) { - print(element.declaringClass.asString()) - print(".") + if (!element.declaringClass.methodsAreAccessibleAsTopLevel) { + print(element.declaringClass.asString()) + print(".") + } print(element.fieldName) } @@ -707,7 +714,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/codegen/model/visitor/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt index 88bbba9ffa..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,6 +60,9 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C override val langPackage: String = "java.lang" + override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + get() = this == context.generatedClass + 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..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 @@ -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,10 @@ 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<*>) { for (annotation in element.annotations) { annotation.accept(this) 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-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/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-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..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 @@ -18,6 +18,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.* 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 +27,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 @@ -218,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 } @@ -231,15 +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 0beca8f9a1..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,9 @@ 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 import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject @@ -24,13 +27,27 @@ 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 ?.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 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). */ 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 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..f9b281a5ce --- /dev/null +++ b/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt @@ -0,0 +1,15 @@ +package org.utbot.examples.codegen + +class CustomClass + +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 this === other +} \ No newline at end of file