diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 20951f7cdf7..fa7f9d55a91 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -88,6 +88,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolve 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.frontend.api.symbols.AbstractSymbolsByPsiBuildingTest import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyWithLibTest import org.jetbrains.kotlin.idea.highlighter.* @@ -935,14 +936,18 @@ fun main(args: Array) { } testGroup("idea/idea-frontend-fir/tests", "idea/idea-frontend-fir/testData") { - testClass { - model("ktDeclarationAndFirDeclarationEqualityChecker") - } + testClass { + model("ktDeclarationAndFirDeclarationEqualityChecker") + } - testClass { - model("analysisSession/resolveCall") + testClass { + model("analysisSession/resolveCall") + } + + testClass { + model("symbolsByPsi") + } } - } testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") { testClass { diff --git a/idea/idea-frontend-api/build.gradle.kts b/idea/idea-frontend-api/build.gradle.kts index 0c60843e91a..3a5bce2fda0 100644 --- a/idea/idea-frontend-api/build.gradle.kts +++ b/idea/idea-frontend-api/build.gradle.kts @@ -4,6 +4,8 @@ plugins { } dependencies { + compileOnly(project(":kotlin-reflect-api")) + compileOnly(project(":compiler:psi")) compileOnly(project(":compiler:frontend")) compileOnly(project(":core:type-system")) diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/DebugSymbolRenderer.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/DebugSymbolRenderer.kt new file mode 100644 index 00000000000..2456bc60e71 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/DebugSymbolRenderer.kt @@ -0,0 +1,54 @@ +/* + * 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.symbols + +import org.jetbrains.kotlin.idea.frontend.api.TypeInfo +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import kotlin.reflect.KProperty +import kotlin.reflect.jvm.javaGetter + +object DebugSymbolRenderer { + fun render(symbol: KtSymbol): String = buildString { + val klass = symbol::class + appendLine("${klass.simpleName}:") + klass.members.filterIsInstance>().sortedBy { it.name }.forEach { property -> + if (property.name in ignoredPropertyNames) return@forEach + val value = property.javaGetter?.invoke(symbol) ?: return@forEach + val stringValue = renderValue(value) + appendLine(" ${property.name}: $stringValue") + } + } + + private fun renderValue(value: Any?): String = when (value) { + null -> "null" + is Boolean -> value.toString() + is Name -> value.asString() + is FqName -> value.asString() + is ClassId -> value.asString() + is Enum<*> -> value.name + is List<*> -> buildString { + append("[") + value.joinTo(this) { renderValue(it) } + append("]") + } + is TypeInfo -> value.asDenotableTypeStringRepresentation() + is KtSymbol -> { + val symbolTag = when (value) { + is KtClassLikeSymbol -> renderValue(value.classId) + is KtFunctionSymbol -> renderValue(value.fqName) + is KtConstructorSymbol -> "" + is KtNamedSymbol -> renderValue(value.name) + else -> TODO(value::class.toString()) + } + "${value::class.simpleName!!}($symbolTag)" + } + else -> value::class.simpleName!! + } + + private val ignoredPropertyNames = setOf("fir", "psi", "token") +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/symbolsByPsi/class.kt b/idea/idea-frontend-fir/testData/symbolsByPsi/class.kt new file mode 100644 index 00000000000..b4c1d196734 --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolsByPsi/class.kt @@ -0,0 +1,13 @@ +class A { +} + +// SYMBOLS: +/* +KtFirClassOrObjectSymbol: + classId: A + classKind: CLASS + name: A + origin: SOURCE + symbolKind: TOP_LEVEL + typeParameters: [] +*/ diff --git a/idea/idea-frontend-fir/testData/symbolsByPsi/classMembes.kt b/idea/idea-frontend-fir/testData/symbolsByPsi/classMembes.kt new file mode 100644 index 00000000000..cfb861cc60e --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolsByPsi/classMembes.kt @@ -0,0 +1,37 @@ +class A { + val a: Int = 10 + fun x() = 10 +} + +// SYMBOLS: +/* +KtFirPropertySymbol: + fqName: A.a + isExtension: false + isVal: true + name: a + origin: SOURCE + receiverType: kotlin/Int + symbolKind: MEMBER + type: kotlin/Int + +KtFirFunctionSymbol: + fqName: A.x + isExtension: false + isOperator: false + isSuspend: false + name: x + origin: SOURCE + symbolKind: MEMBER + type: kotlin/Int + typeParameters: [] + valueParameters: [] + +KtFirClassOrObjectSymbol: + classId: A + classKind: CLASS + name: A + origin: SOURCE + symbolKind: TOP_LEVEL + typeParameters: [] +*/ diff --git a/idea/idea-frontend-fir/testData/symbolsByPsi/classWithTypeParams.kt b/idea/idea-frontend-fir/testData/symbolsByPsi/classWithTypeParams.kt new file mode 100644 index 00000000000..7e58beb4b24 --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolsByPsi/classWithTypeParams.kt @@ -0,0 +1,21 @@ +class A { +} + +// SYMBOLS: +/* +KtFirTypeParameterSymbol: + name: T + origin: SOURCE + +KtFirTypeParameterSymbol: + name: R + origin: SOURCE + +KtFirClassOrObjectSymbol: + classId: A + classKind: CLASS + name: A + origin: SOURCE + symbolKind: TOP_LEVEL + typeParameters: [KtFirTypeParameterSymbol(T), KtFirTypeParameterSymbol(R)] +*/ diff --git a/idea/idea-frontend-fir/testData/symbolsByPsi/extensionFunction.kt b/idea/idea-frontend-fir/testData/symbolsByPsi/extensionFunction.kt new file mode 100644 index 00000000000..b5099186bac --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolsByPsi/extensionFunction.kt @@ -0,0 +1,17 @@ +fun String.foo(): Int = 10 + +// SYMBOLS: +/* +KtFirFunctionSymbol: + fqName: foo + isExtension: true + isOperator: false + isSuspend: false + name: foo + origin: SOURCE + receiverType: kotlin/String + symbolKind: TOP_LEVEL + type: kotlin/Int + typeParameters: [] + valueParameters: [] +*/ diff --git a/idea/idea-frontend-fir/testData/symbolsByPsi/function.kt b/idea/idea-frontend-fir/testData/symbolsByPsi/function.kt new file mode 100644 index 00000000000..248285f9e2b --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolsByPsi/function.kt @@ -0,0 +1,23 @@ +fun foo(x: Int) {} + +// SYMBOLS: +/* +KtFirFunctionValueParameterSymbol: + isVararg: false + name: x + origin: SOURCE + symbolKind: LOCAL + type: kotlin/Int + +KtFirFunctionSymbol: + fqName: foo + isExtension: false + isOperator: false + isSuspend: false + name: foo + origin: SOURCE + symbolKind: TOP_LEVEL + type: kotlin/Unit + typeParameters: [] + valueParameters: [KtFirFunctionValueParameterSymbol(x)] +*/ diff --git a/idea/idea-frontend-fir/testData/symbolsByPsi/functionWithTypeParams.kt b/idea/idea-frontend-fir/testData/symbolsByPsi/functionWithTypeParams.kt new file mode 100644 index 00000000000..8895da9eec2 --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolsByPsi/functionWithTypeParams.kt @@ -0,0 +1,27 @@ +fun foo(x: X) {} + +// SYMBOLS: +/* +KtFirTypeParameterSymbol: + name: X + origin: SOURCE + +KtFirFunctionValueParameterSymbol: + isVararg: false + name: x + origin: SOURCE + symbolKind: LOCAL + type: X + +KtFirFunctionSymbol: + fqName: foo + isExtension: false + isOperator: false + isSuspend: false + name: foo + origin: SOURCE + symbolKind: TOP_LEVEL + type: kotlin/Unit + typeParameters: [KtFirTypeParameterSymbol(X)] + valueParameters: [KtFirFunctionValueParameterSymbol(x)] +*/ diff --git a/idea/idea-frontend-fir/testData/symbolsByPsi/implicitReturn.kt b/idea/idea-frontend-fir/testData/symbolsByPsi/implicitReturn.kt new file mode 100644 index 00000000000..8f29d490cfb --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolsByPsi/implicitReturn.kt @@ -0,0 +1,16 @@ +fun foo(): Int = 10 + +// SYMBOLS: +/* +KtFirFunctionSymbol: + fqName: foo + isExtension: false + isOperator: false + isSuspend: false + name: foo + origin: SOURCE + symbolKind: TOP_LEVEL + type: kotlin/Int + typeParameters: [] + valueParameters: [] +*/ 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 index 25ed4c60263..79f583921f9 100644 --- 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 @@ -5,28 +5,21 @@ 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.addExternalTestFiles +import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction import org.jetbrains.kotlin.idea.frontend.api.CallInfo import org.jetbrains.kotlin.idea.frontend.api.TypeInfo -import org.jetbrains.kotlin.idea.frontend.api.symbols.* -import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtParameterSymbol 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.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtFile 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 @@ -39,7 +32,7 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCod override fun getTestDataPath(): String = KotlinTestUtils.getHomeDirectory() + "/" protected fun doTest(path: String) { - File(path).getExternalFiles().forEach(::addFile) + addExternalTestFiles(path) configureByFile(path) val elements = editor.caretModel.caretsAndSelections.map { selection -> getSingleSelectedElement(selection) @@ -89,29 +82,6 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCod 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 { diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/AbstractSymbolsByPsiBuildingTest.kt b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/AbstractSymbolsByPsiBuildingTest.kt new file mode 100644 index 00000000000..ec668d14368 --- /dev/null +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/AbstractSymbolsByPsiBuildingTest.kt @@ -0,0 +1,51 @@ +/* + * 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.symbols + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiRecursiveElementWalkingVisitor +import org.jetbrains.kotlin.idea.addExternalTestFiles +import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction +import org.jetbrains.kotlin.idea.frontend.api.fir.FirAnalysisSession +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File + +abstract class AbstractSymbolsByPsiBuildingTest : KotlinLightCodeInsightFixtureTestCase() { + protected fun doTest(path: String) { + addExternalTestFiles(path) + val file = File(path) + val ktFile = myFixture.configureByText(file.name, FileUtil.loadFile(file)) as KtFile + + val renderedSymbols = executeOnPooledThreadInReadAction { + val analysisSession = FirAnalysisSession(ktFile) + val declarationSymbols = ktFile.collectDescendantsOfType().map { declaration -> + analysisSession.symbolProvider.getSymbol(declaration) + } + declarationSymbols.map(DebugSymbolRenderer::render) + } + + val actual = buildString { + val actualSymbolsData = renderedSymbols.joinToString(separator = "\n") + val fileTextWithoutSymbolsData = ktFile.text.substringBeforeLast(SYMBOLS_TAG).trimEnd() + appendLine(fileTextWithoutSymbolsData) + appendLine() + appendLine(SYMBOLS_TAG) + appendLine("/*") + append(actualSymbolsData) + appendLine("*/") + } + KotlinTestUtils.assertEqualsToFile(file, actual) + + } + + companion object { + private const val SYMBOLS_TAG = "// SYMBOLS:" + } +} diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/SymbolsByPsiBuildingTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/SymbolsByPsiBuildingTestGenerated.java new file mode 100644 index 00000000000..46570b2309b --- /dev/null +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/SymbolsByPsiBuildingTestGenerated.java @@ -0,0 +1,65 @@ +/* + * 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.symbols; + +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/symbolsByPsi") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class SymbolsByPsiBuildingTestGenerated extends AbstractSymbolsByPsiBuildingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInSymbolsByPsi() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/symbolsByPsi"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("class.kt") + public void testClass() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolsByPsi/class.kt"); + } + + @TestMetadata("classMembes.kt") + public void testClassMembes() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolsByPsi/classMembes.kt"); + } + + @TestMetadata("classWithTypeParams.kt") + public void testClassWithTypeParams() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolsByPsi/classWithTypeParams.kt"); + } + + @TestMetadata("extensionFunction.kt") + public void testExtensionFunction() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolsByPsi/extensionFunction.kt"); + } + + @TestMetadata("function.kt") + public void testFunction() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolsByPsi/function.kt"); + } + + @TestMetadata("functionWithTypeParams.kt") + public void testFunctionWithTypeParams() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolsByPsi/functionWithTypeParams.kt"); + } + + @TestMetadata("implicitReturn.kt") + public void testImplicitReturn() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolsByPsi/implicitReturn.kt"); + } +} diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/testUtils.kt b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/testUtils.kt index 3c5bc2f72e3..d24e8689305 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/testUtils.kt +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/testUtils.kt @@ -5,8 +5,41 @@ package org.jetbrains.kotlin.idea -import org.jetbrains.kotlin.idea.fir.low.level.api.DuplicatedFirSourceElementsException +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.util.io.FileUtil +import com.intellij.testFramework.LightPlatformTestCase +import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.idea.fir.low.level.api.DuplicatedFirSourceElementsException +import java.io.File + +fun executeOnPooledThreadInReadAction(action: () -> R): R = + ApplicationManager.getApplication().executeOnPooledThread { runReadAction(action) }.get() + +fun addExternalTestFiles(testDataFilePath: String) { + File(testDataFilePath).getExternalFiles().forEach(::addFile) +} + +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 File.getExternalFiles(): List { + val directory = parentFile + val externalFileName = "${nameWithoutExtension}.external" + return directory.listFiles { _, name -> + name == "$externalFileName.kt" || name == "$externalFileName.java" + }!!.filterNotNull() +} /** * Temporary