diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index fc36aa6cd17..512185ca7b3 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -85,6 +85,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyResolveTest import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyResolveTest import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolveTest import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostic.AbstractDiagnosticTraversalCounterTest +import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostic.AbstractFirContextCollectionTest import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureTest import org.jetbrains.kotlin.idea.fir.low.level.api.resolve.AbstractInnerDeclarationsResolvePhaseTest @@ -1084,6 +1085,9 @@ fun main(args: Array) { testClass { model("fileStructure") } + testClass { + model("fileStructure") + } testClass { model("diagnosticTraversalCounter") } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/BeforeElementDiagnosticCollectionHandler.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/BeforeElementDiagnosticCollectionHandler.kt index a75a638b4c5..9e112782e7d 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/BeforeElementDiagnosticCollectionHandler.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/BeforeElementDiagnosticCollectionHandler.kt @@ -8,9 +8,12 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSessionComponent +import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext +import org.jetbrains.kotlin.fir.declarations.FirDeclaration abstract class BeforeElementDiagnosticCollectionHandler: FirSessionComponent { - abstract fun beforeCollectingForElement(element: FirElement) + open fun beforeCollectingForElement(element: FirElement) {} + open fun beforeGoingNestedDeclaration(declaration: FirDeclaration, context: PersistentCheckerContext) {} } val FirSession.beforeElementDiagnosticCollectionHandler: BeforeElementDiagnosticCollectionHandler? by FirSession.nullableSessionComponentAccessor() diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeDiagnosticVisitor.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeDiagnosticVisitor.kt index f8209871591..88622fec695 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeDiagnosticVisitor.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeDiagnosticVisitor.kt @@ -9,8 +9,7 @@ import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext import org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor import org.jetbrains.kotlin.fir.analysis.collectors.components.AbstractDiagnosticCollectorComponent -import org.jetbrains.kotlin.fir.resolve.SessionHolder -import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir.PersistentCheckerContextFactory +import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.idea.fir.low.level.api.util.checkCanceled internal open class FirIdeDiagnosticVisitor( @@ -21,6 +20,13 @@ internal open class FirIdeDiagnosticVisitor( checkCanceled() } + override fun goToNestedElements(element: FirElement) { + if (element is FirDeclaration) { + session.beforeElementDiagnosticCollectionHandler?.beforeGoingNestedDeclaration(element, context) + } + super.goToNestedElements(element) + } + override fun beforeRunningAllComponentsOnElement(element: FirElement) { session.beforeElementDiagnosticCollectionHandler?.beforeCollectingForElement(element) } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt index 44b9136bd96..93f27b78a2b 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt @@ -80,7 +80,7 @@ internal class FileStructure( } } - private fun getAllStructureElements(): Collection { + fun getAllStructureElements(): Collection { val structureElements = mutableSetOf(getStructureElementFor(ktFile)) ktFile.accept(object : KtVisitorVoid() { override fun visitElement(element: PsiElement) { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt index bcb267d4caf..269d7e2d568 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt @@ -30,7 +30,7 @@ internal sealed class FileStructureElement(val firFile: FirFile, protected val l internal sealed class ReanalyzableStructureElement>( firFile: FirFile, - protected val firSymbol: S, + val firSymbol: S, lockProvider: LockProvider, ) : FileStructureElement(firFile, lockProvider) { abstract override val psi: KtDeclaration @@ -50,7 +50,11 @@ internal sealed class ReanalyzableStructureElement, ) : FileStructureElement(firFile, lockProvider) { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/AbstractFirContextCollectionTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/AbstractFirContextCollectionTest.kt new file mode 100644 index 00000000000..2c8a4247d8b --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/AbstractFirContextCollectionTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2010-2021 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.fir.low.level.api.diagnostic + +import com.intellij.openapi.util.io.FileUtil +import kotlinx.collections.immutable.PersistentList +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.SessionConfiguration +import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.resolve.PersistentImplicitReceiverStack +import org.jetbrains.kotlin.fir.resolve.SessionHolderImpl +import org.jetbrains.kotlin.idea.caches.project.getModuleInfo +import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveStateImpl +import org.jetbrains.kotlin.idea.fir.low.level.api.api.DiagnosticCheckerFilter +import org.jetbrains.kotlin.idea.fir.low.level.api.api.getDiagnostics +import org.jetbrains.kotlin.idea.fir.low.level.api.api.getFirFile +import org.jetbrains.kotlin.idea.fir.low.level.api.createResolveStateForNoCaching +import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.BeforeElementDiagnosticCollectionHandler +import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir.PersistenceContextCollector +import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.* +import org.jetbrains.kotlin.idea.fir.low.level.api.name +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.psi.KtFile +import java.io.File + +abstract class AbstractFirContextCollectionTest : KotlinLightCodeInsightFixtureTestCase() { + fun doTest(path: String) { + val testDataFile = File(path) + val ktFile = myFixture.configureByText(testDataFile.name, FileUtil.loadFile(testDataFile)) as KtFile + + val handler = BeforeElementTestDiagnosticCollectionHandler() + + @OptIn(SessionConfiguration::class) + val resolveState = createResolveStateForNoCaching(ktFile.getModuleInfo()) { + register(BeforeElementDiagnosticCollectionHandler::class, handler) + } as FirModuleResolveStateImpl + + val fileStructure = resolveState.fileStructureCache.getFileStructure(ktFile, resolveState.rootModuleSession.cache) + val allStructureElements = fileStructure.getAllStructureElements() + handler.elementsToCheckContext = allStructureElements.map { it.getFirDeclaration() } + handler.firFile = ktFile.getFirFile(resolveState) + ktFile.getDiagnostics(resolveState, DiagnosticCheckerFilter.ONLY_COMMON_CHECKERS) + } + + private fun FileStructureElement.getFirDeclaration(): FirDeclaration = when (this) { + is NonReanalyzableDeclarationStructureElement -> fir + is ReanalyzableStructureElement<*, *> -> firSymbol.fir as FirDeclaration + is RootStructureElement -> firFile + } + + + private class BeforeElementTestDiagnosticCollectionHandler : BeforeElementDiagnosticCollectionHandler() { + lateinit var elementsToCheckContext: List + lateinit var firFile: FirFile + + override fun beforeGoingNestedDeclaration(declaration: FirDeclaration, context: PersistentCheckerContext) { + if (declaration is FirFile) { + return + } + if (declaration in elementsToCheckContext) { + val collectedContext = PersistenceContextCollector.collectContext( + SessionHolderImpl.createWithEmptyScopeSession(declaration.session), + firFile, + declaration + ) + compareStructurally(context, collectedContext) + } + } + + private fun compareStructurally(expected: PersistentCheckerContext, actual: PersistentCheckerContext) { + assertEquals(expected.implicitReceiverStack.asString(), actual.implicitReceiverStack.asString()) + assertEquals(expected.containingDeclarations.asString(), actual.containingDeclarations.asString()) + } + + private fun PersistentImplicitReceiverStack.asString() = + joinToString { it.boundSymbol.name() } + + private fun PersistentList.asString() = + joinToString(transform = FirDeclaration::name) + } +} + diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/FirContextCollectionTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/FirContextCollectionTestGenerated.java new file mode 100644 index 00000000000..5e9cf92e78a --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/FirContextCollectionTestGenerated.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010-2021 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.fir.low.level.api.diagnostic; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.util.KtTestUtil; +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/idea-fir-low-level-api/testdata/fileStructure") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class FirContextCollectionTestGenerated extends AbstractFirContextCollectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInFileStructure() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("class.kt") + public void testClass() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/class.kt"); + } + + @TestMetadata("classMemberProperty.kt") + public void testClassMemberProperty() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/classMemberProperty.kt"); + } + + @TestMetadata("declarationsInPropertyInit.kt") + public void testDeclarationsInPropertyInit() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/declarationsInPropertyInit.kt"); + } + + @TestMetadata("enumClass.kt") + public void testEnumClass() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/enumClass.kt"); + } + + @TestMetadata("enumClassWithBody.kt") + public void testEnumClassWithBody() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/enumClassWithBody.kt"); + } + + @TestMetadata("initBlock.kt") + public void testInitBlock() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/initBlock.kt"); + } + + @TestMetadata("localClass.kt") + public void testLocalClass() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localClass.kt"); + } + + @TestMetadata("localFun.kt") + public void testLocalFun() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localFun.kt"); + } + + @TestMetadata("localProperty.kt") + public void testLocalProperty() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localProperty.kt"); + } + + @TestMetadata("nestedClasses.kt") + public void testNestedClasses() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/nestedClasses.kt"); + } + + @TestMetadata("propertyAccessors.kt") + public void testPropertyAccessors() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/propertyAccessors.kt"); + } + + @TestMetadata("topLevelExpressionBodyFunWithType.kt") + public void testTopLevelExpressionBodyFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithType.kt"); + } + + @TestMetadata("topLevelExpressionBodyFunWithoutType.kt") + public void testTopLevelExpressionBodyFunWithoutType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithoutType.kt"); + } + + @TestMetadata("topLevelFunWithType.kt") + public void testTopLevelFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelFunWithType.kt"); + } + + @TestMetadata("topLevelProperty.kt") + public void testTopLevelProperty() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelProperty.kt"); + } + + @TestMetadata("topLevelUnitFun.kt") + public void testTopLevelUnitFun() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelUnitFun.kt"); + } + + @TestMetadata("typeAlias.kt") + public void testTypeAlias() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/typeAlias.kt"); + } +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/firTestUtils.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/firTestUtils.kt index fec79bff1cf..bc1bbc11093 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/firTestUtils.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/firTestUtils.kt @@ -11,7 +11,14 @@ import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirRenderer +import org.jetbrains.kotlin.fir.FirSymbolOwner +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousInitializerSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.KotlinFirModificationTrackerService @@ -29,4 +36,17 @@ internal fun FirElement.renderWithClassName(renderMode: FirRenderer.RenderMode = internal fun Module.incModificationTracker() { project.service().increaseModificationCountForModule(this) +} + +internal fun AbstractFirBasedSymbol<*>.name(): String = when (this) { + is FirCallableSymbol<*> -> callableId.callableName.asString() + is FirClassLikeSymbol<*> -> classId.shortClassName.asString() + is FirAnonymousInitializerSymbol -> "" + else -> error("unknown symbol ${this::class.simpleName}") +} + +internal fun FirDeclaration.name(): String = when (this) { + is FirSymbolOwner<*> -> symbol.name() + is FirFile -> "" + else -> error("unknown declaration ${this::class.simpleName}") } \ No newline at end of file