From fb8acf8c1e2e3df67c7bd9609821f0be872c050b Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Mon, 8 Jun 2020 22:11:08 +0300 Subject: [PATCH] FIR IDE: add tests for call resolve --- .../kotlin/generators/tests/GenerateTests.kt | 17 +- idea/idea-frontend-fir/build.gradle.kts | 2 + .../api/FirLazyResolveTestGenerated.java | 2 +- .../FirMultiModuleResolveTestGenerated.java | 2 +- .../resolveCall/functionCallInTheSameFile.kt | 7 + .../resolveCall/functionWithReceiverCall.kt | 7 + .../functionWithReceiverSafeCall.kt | 7 + .../resolveCall/implicitConstuctorCall.kt | 7 + .../implicitJavaConstuctorCall.external.java | 3 + .../resolveCall/implicitJavaConstuctorCall.kt | 6 + .../javaFunctionCall.external.java | 3 + .../resolveCall/javaFunctionCall.kt | 6 + .../resolveCall/variableAsFunction.kt | 5 + .../resolveCall/variableAsFunctionLikeCall.kt | 7 + .../api/fir/AbstractResolveCallTest.kt | 154 ++++++++++++++++++ .../api/fir/ResolveCallTestGenerated.java | 70 ++++++++ 16 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.external.java create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.external.java create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt create mode 100644 idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt create mode 100644 idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt create mode 100644 idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index cbd9b7a7cb6..93e0f57d556 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -87,6 +87,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyResolveTest import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolveTest import org.jetbrains.kotlin.idea.fir.AbstractKtDeclarationAndFirDeclarationEqualityChecker import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest +import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyWithLibTest import org.jetbrains.kotlin.idea.highlighter.* @@ -923,20 +924,26 @@ fun main(args: Array) { testClass { model("ktDeclarationAndFirDeclarationEqualityChecker") } + + testClass { + model("analysisSession/resolveCall") + } } - testGroup("idea/idea-fir/tests", "idea/testData") { + testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") { testClass { model("fir/multiModule", recursive = false, extension = null) } - testClass { - model("highlighter") - } - testClass { model("fir/lazyResolve", extension = "test", singleClass = true, filenameStartsLowerCase = true) } + } + + testGroup("idea/idea-fir/tests", "idea/testData") { + testClass { + model("highlighter") + } testClass { model("resolve/references", pattern = KT_WITHOUT_DOTS_IN_NAME) diff --git a/idea/idea-frontend-fir/build.gradle.kts b/idea/idea-frontend-fir/build.gradle.kts index f9b563f4fb5..a5765895ec7 100644 --- a/idea/idea-frontend-fir/build.gradle.kts +++ b/idea/idea-frontend-fir/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { compile(project(":idea:idea-core")) // + testCompile(intellijDep()) + testCompile(intellijCoreDep()) testCompile(toolsJar()) testCompile(projectTests(":idea")) testCompile(projectTests(":compiler:tests-common")) diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirLazyResolveTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirLazyResolveTestGenerated.java index b9d5f309390..fe203a2cb91 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirLazyResolveTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirLazyResolveTestGenerated.java @@ -17,7 +17,7 @@ import java.util.regex.Pattern; /** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @TestMetadata("idea/testData/fir/lazyResolve") -@TestDataPath("/") +@TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public class FirLazyResolveTestGenerated extends AbstractFirLazyResolveTest { private void runTest(String testDataFilePath) throws Exception { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleResolveTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleResolveTestGenerated.java index 39a40932b6e..3e9efca83c0 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleResolveTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleResolveTestGenerated.java @@ -17,7 +17,7 @@ import java.util.regex.Pattern; /** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @TestMetadata("idea/testData/fir/multiModule") -@TestDataPath("/") +@TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public class FirMultiModuleResolveTestGenerated extends AbstractFirMultiModuleResolveTest { private void runTest(String testDataFilePath) throws Exception { diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt new file mode 100644 index 00000000000..0d64c6ea5e8 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt @@ -0,0 +1,7 @@ +fun function(a: Int) {} + +fun call() { + function(1) +} + +// CALL: SimpleKtFunctionCallInfo: targetFunction = function(a: Int): IMPLICIT_TYPE \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt new file mode 100644 index 00000000000..925fafed6f6 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt @@ -0,0 +1,7 @@ +fun String.function(a: Int) {} + +fun call() { + "str".function(1) +} + +// CALL: SimpleKtFunctionCallInfo: targetFunction = function( : String, a: Int): IMPLICIT_TYPE \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt new file mode 100644 index 00000000000..7ec3271c80a --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt @@ -0,0 +1,7 @@ +fun String.function(a: Int) {} + +fun call() { + "str"?.function(1) +} + +// CALL: SimpleKtFunctionCallInfo: targetFunction = function( : String, a: Int): IMPLICIT_TYPE \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt new file mode 100644 index 00000000000..6832ad518b9 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt @@ -0,0 +1,7 @@ +class A + +fun call() { + val a = A() +} + +// CALL: KtImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.external.java b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.external.java new file mode 100644 index 00000000000..1b4568a8f26 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.external.java @@ -0,0 +1,3 @@ +class A { + +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt new file mode 100644 index 00000000000..36429367409 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt @@ -0,0 +1,6 @@ + +fun call() { + val a = A() +} + +// CALL: JavaImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.external.java b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.external.java new file mode 100644 index 00000000000..2471106ae97 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.external.java @@ -0,0 +1,3 @@ +class JavaClass { + void javaMethod() {} +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt new file mode 100644 index 00000000000..391cb3e03c6 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt @@ -0,0 +1,6 @@ +fun call() { + val javaClass = JavaClass() + javaClass.javaMethod() +} + +// CALL: SimpleJavaFunctionCallInfo: targetFunction = JavaClass.javaMethod(): void \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt new file mode 100644 index 00000000000..52b2df848fb --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt @@ -0,0 +1,5 @@ +fun call(x: (Int) -> String) { + x(1) +} + +// CALL: VariableAsFunctionCallInfo: target = x, isSuspendCall = false \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt new file mode 100644 index 00000000000..4e1c191f86f --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt @@ -0,0 +1,7 @@ +operator fun Int.invoke(): String {} + +fun call(x: Int) { + x() +} + +// CALL: SimpleKtFunctionCallInfo: targetFunction = invoke( : Int): String \ No newline at end of file diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt new file mode 100644 index 00000000000..51498c9394a --- /dev/null +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.frontend.api.fir + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.editor.CaretState +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiParameter +import com.intellij.testFramework.LightPlatformTestCase +import org.intellij.plugins.relaxNG.compact.psi.util.PsiFunction +import org.jetbrains.kotlin.idea.frontend.api.CallInfo +import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightTestCase +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.elementsInRange +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.javaGetter + + +abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCodeInsightTestCase() { + override fun getTestDataPath(): String = KotlinTestUtils.getHomeDirectory() + "/" + + protected fun doTest(path: String) { + File(path).getExternalFiles().forEach(::addFile) + configureByFile(path) + val elements = editor.caretModel.caretsAndSelections.map { selection -> + getSingleSelectedElement(selection) + } + val callInfos = executeOnPooledThreadInReadAction { + val analysisSession = FirAnalysisSession(file as KtFile) + elements.map { element -> + when (element) { + is KtCallExpression -> analysisSession.resolveCall(element) + is KtBinaryExpression -> analysisSession.resolveCall(element) + else -> error("Selected should be either KtCallExpression or KtBinaryExpression but was $element") + } + } + } + if (callInfos.isEmpty()) { + error("There are should be at least one call selected") + } + + val textWithoutLatestComments = run { + val rawText = File(path).readText() + """(?m)^// CALL:\s.*$""".toRegex().replace(rawText, "").trimEnd() + } + val actualText = buildString { + append(textWithoutLatestComments) + append("\n\n") + callInfos.joinTo(this, separator = "\n") { info -> + "// CALL: ${info?.stringRepresentation()}" + } + } + KotlinTestUtils.assertEqualsToFile(File(path), actualText) + } + + + private fun getSingleSelectedElement(selection: CaretState): PsiElement { + val selectionRange = selection.getTextRange() + val elements = file.elementsInRange(selectionRange) + if (elements.size != 1) { + val selectionText = file.text.substring(selectionRange.startOffset, selectionRange.endOffset) + error("Single element should be found for selection `$selectionText`, but $elements were found") + } + return elements.first() + } + + private fun CaretState.getTextRange() = TextRange.create( + editor.logicalPositionToOffset(selectionStart!!), + editor.logicalPositionToOffset(selectionEnd!!) + ) + + private fun addFile(file: File) { + addFile(FileUtil.loadFile(file, /* convertLineSeparators = */true), file.name) + } + + private fun addFile(text: String, fileName: String) { + runWriteAction { + val virtualDir = LightPlatformTestCase.getSourceRoot()!! + val virtualFile = virtualDir.createChildData(null, fileName) + virtualFile.getOutputStream(null)!!.writer().use { it.write(text) } + } + } +} + +private fun executeOnPooledThreadInReadAction(action: () -> R): R = + ApplicationManager.getApplication().executeOnPooledThread { runReadAction(action) }.get() + +private fun File.getExternalFiles(): List { + val directory = parentFile + val externalFileName = "${nameWithoutExtension}.external" + return directory.listFiles { _, name -> + name == "$externalFileName.kt" || name == "$externalFileName.java" + }!!.filterNotNull() +} + +private fun CallInfo.stringRepresentation(): String { + fun Any.stringValue(): String? = when (this) { + is PsiMethod -> buildString { + append(getKotlinFqName()!!) + @Suppress("UnstableApiUsage") + parameters.joinTo(this, prefix = "(", postfix = ")") { parameter -> + "${parameter.name}: ${(parameter as PsiParameter).typeElement!!.text}" + } + append(": ${returnTypeElement!!.text}") + } + is KtFunction -> buildString { + append(getKotlinFqName()!!) + append("(") + receiverTypeReference?.let { receiver -> + append(" : ${receiver.text}") + if (valueParameters.isNotEmpty()) append(", ") + } + valueParameters.joinTo(this,) { parameter -> + "${parameter.name}: ${parameter.typeReference!!.text}" + } + append(")") + append(": ${typeReference?.text ?: "IMPLICIT_TYPE"}") + } + is KtClass -> "Implicit constructor of ${getKotlinFqName()!!}" + is PsiClass -> "Implicit constructor of ${getKotlinFqName()!!}" + is KtParameter -> name!! + is Boolean -> toString() + else -> error("unexpected parameter type ${this::class}") + } + + val callInfoClass = this::class + return buildString { + append(callInfoClass.simpleName!!) + append(": ") + val propertyByName = callInfoClass.memberProperties.associateBy(KProperty1<*, *>::name) + callInfoClass.primaryConstructor!!.parameters.joinTo(this) { parameter -> + val value = propertyByName[parameter.name]!!.javaGetter!!(this@stringRepresentation)?.stringValue() + "${parameter.name!!} = $value" + } + } +} + diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java new file mode 100644 index 00000000000..1ce83128cf9 --- /dev/null +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.frontend.api.fir; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/idea-frontend-fir/testData/analysisSession/resolveCall") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ResolveCallTestGenerated extends AbstractResolveCallTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInResolveCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/analysisSession/resolveCall"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("functionCallInTheSameFile.kt") + public void testFunctionCallInTheSameFile() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt"); + } + + @TestMetadata("functionWithReceiverCall.kt") + public void testFunctionWithReceiverCall() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt"); + } + + @TestMetadata("functionWithReceiverSafeCall.kt") + public void testFunctionWithReceiverSafeCall() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt"); + } + + @TestMetadata("implicitConstuctorCall.kt") + public void testImplicitConstuctorCall() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt"); + } + + @TestMetadata("implicitJavaConstuctorCall.kt") + public void testImplicitJavaConstuctorCall() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt"); + } + + @TestMetadata("javaFunctionCall.kt") + public void testJavaFunctionCall() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt"); + } + + @TestMetadata("variableAsFunction.kt") + public void testVariableAsFunction() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt"); + } + + @TestMetadata("variableAsFunctionLikeCall.kt") + public void testVariableAsFunctionLikeCall() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt"); + } +}