Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 45
Add support for top-level Kotlin functions #847#1147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
d81b12e585023981880d600372aea3538dacfe5932File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<Metadata>().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 | ||
EgorkaKulikov marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| is CallableReference -> owner as? KClass<*> | ||
| else -> instanceParameter?.type?.classifier as? KClass<*> | ||
| }?.java ?: tryConstructor(this) ?: error("Can't get parent class for $this") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<Class<*>>(kotlin.jvm.internal.Intrinsics::class.java) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() | ||
EgorkaKulikov marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } 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)) { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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("(") | ||
EgorkaKulikov marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| caller.accept(this) | ||
| if (caller !is CgVariable) print(")") | ||
| renderAccess(caller) | ||
| } | ||
| } else { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<PsiClass> { | ||
| 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() }) | ||
EgorkaKulikov marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| for (psiDir in psiDirectories) allClasses += getAllClasses(psiDir) | ||
| return allClasses | ||
| } | ||
| private fun getClassesFromFile(psiFile: PsiFile): List<PsiClass> { | ||
| val psiElementHandler = PsiElementHandler.makePsiElementHandler(psiFile) | ||
| return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, psiElementHandler.classClass) | ||
| .map { psiElementHandler.toPsi(it, PsiClass::class.java) } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.