From 70c98d34d205939fa259e986754810f6abb690b4 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Fri, 26 Feb 2021 17:51:13 +0100 Subject: [PATCH] FIR IDE: add test which check that every declaration is visited single time during diagnostic collection --- .../collectors/AbstractDiagnosticCollector.kt | 6 +- .../kotlin/generators/tests/GenerateTests.kt | 4 + .../level/api/FirIdeResolveStateService.kt | 15 ++- .../AbstractFirIdeDiagnosticsCollector.kt | 10 +- ...eforeElementDiagnosticCollectionHandler.kt | 16 +++ .../api/sessions/FirIdeSessionFactory.kt | 13 +- .../sessions/FirIdeSessionProviderStorage.kt | 6 +- .../diagnosticTraversalCounter/constructor.kt | 3 + .../functionalType.kt | 3 + .../localFunctionWithImplicitType.kt | 3 + .../localUnitFunction.kt | 5 + .../memberFunctions.kt | 7 ++ .../memberProperties.kt | 7 ++ .../multipleTopLevelClasses.kt | 15 +++ ...tipleTopLevelFunctionsWithImplicitTypes.kt | 7 ++ .../multipleTopLevelUnitFunctions.kt | 15 +++ .../nestedClases.kt | 11 ++ .../propertyWithGetterAndSetter.kt | 5 + .../secondaryConstructor.kt | 3 + .../AbstractDiagnosticTraversalCounterTest.kt | 112 ++++++++++++++++++ ...agnosticTraversalCounterTestGenerated.java | 91 ++++++++++++++ .../idea/fir/low/level/api/firTestUtils.kt | 6 + 22 files changed, 353 insertions(+), 10 deletions(-) create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/BeforeElementDiagnosticCollectionHandler.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/constructor.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/functionalType.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localFunctionWithImplicitType.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localUnitFunction.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberFunctions.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberProperties.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelClasses.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelFunctionsWithImplicitTypes.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelUnitFunctions.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/nestedClases.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/propertyWithGetterAndSetter.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/secondaryConstructor.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/AbstractDiagnosticTraversalCounterTest.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/DiagnosticTraversalCounterTestGenerated.java diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollector.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollector.kt index 62e78f787bc..60b6d677c03 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollector.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/AbstractDiagnosticCollector.kt @@ -68,13 +68,15 @@ abstract class AbstractDiagnosticCollector( componentsInitialized = true } - protected open fun beforeCollecting() {} + protected open fun beforeRunningAllComponentsOnElement(element: FirElement) {} + protected open fun beforeRunningSingleComponentOnElement(element: FirElement) {} private inner class Visitor : FirDefaultVisitor() { private fun T.runComponents() { if (currentAction.checkInCurrentDeclaration) { + beforeRunningAllComponentsOnElement(this) components.forEach { - beforeCollecting() + beforeRunningSingleComponentOnElement(this) this.accept(it, context) } } diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 77f93a55784..d5469712fb2 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -83,6 +83,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyDeclarationRes 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.file.structure.AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureTest import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.AbstractSessionsInvalidationTest @@ -1073,6 +1074,9 @@ fun main(args: Array) { testClass { model("fileStructure") } + testClass { + model("diagnosticTraversalCounter") + } testClass { model("sessionInvalidation", recursive = false, extension = null) } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt index 8df644d00f9..e2e40090235 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt @@ -13,8 +13,8 @@ import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver +import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSessionProviderStorage -import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.KotlinFirOutOfBlockModificationTrackerFactory import org.jetbrains.kotlin.idea.util.cachedValue import org.jetbrains.kotlin.idea.util.getValue import org.jetbrains.kotlin.trackers.createProjectWideOutOfBlockModificationTracker @@ -39,12 +39,13 @@ internal class FirIdeResolveStateService(project: Project) { internal fun createResolveStateFor( moduleInfo: IdeaModuleInfo, - sessionProviderStorage: FirIdeSessionProviderStorage + sessionProviderStorage: FirIdeSessionProviderStorage, + configureSession: (FirIdeSession.() -> Unit)? = null, ): FirModuleResolveStateImpl { if (moduleInfo !is ModuleSourceInfo) { error("Creating FirModuleResolveState is not yet supported for $moduleInfo") } - val sessionProvider = sessionProviderStorage.getSessionProvider(moduleInfo) + val sessionProvider = sessionProviderStorage.getSessionProvider(moduleInfo, configureSession) val firFileBuilder = sessionProvider.rootModuleSession.firFileBuilder return FirModuleResolveStateImpl( moduleInfo.project, @@ -60,6 +61,12 @@ internal class FirIdeResolveStateService(project: Project) { @TestOnly fun createResolveStateForNoCaching( moduleInfo: IdeaModuleInfo, -): FirModuleResolveState = FirIdeResolveStateService.createResolveStateFor(moduleInfo, FirIdeSessionProviderStorage(moduleInfo.project!!)) + configureSession: (FirIdeSession.() -> Unit)? = null, +): FirModuleResolveState = + FirIdeResolveStateService.createResolveStateFor( + moduleInfo = moduleInfo, + sessionProviderStorage = FirIdeSessionProviderStorage(moduleInfo.project!!), + configureSession = configureSession + ) diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/AbstractFirIdeDiagnosticsCollector.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/AbstractFirIdeDiagnosticsCollector.kt index e8d1ecc99b4..11ad90ce80a 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/AbstractFirIdeDiagnosticsCollector.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/AbstractFirIdeDiagnosticsCollector.kt @@ -5,6 +5,7 @@ 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.SessionConfiguration import org.jetbrains.kotlin.fir.analysis.CheckersComponent @@ -41,6 +42,9 @@ internal abstract class AbstractFirIdeDiagnosticsCollector( ::FirIdeDesignatedBodyResolveTransformerForReturnTypeCalculator ) ) { + private val beforeElementDiagnosticCollectionHandler: BeforeElementDiagnosticCollectionHandler? = + session.beforeElementDiagnosticCollectionHandler + init { val declarationCheckers = CheckersFactory.createDeclarationCheckers(useExtendedCheckers) val expressionCheckers = CheckersFactory.createExpressionCheckers(useExtendedCheckers) @@ -70,10 +74,14 @@ internal abstract class AbstractFirIdeDiagnosticsCollector( reporter = Reporter() } - override fun beforeCollecting() { + override fun beforeRunningSingleComponentOnElement(element: FirElement) { checkCanceled() } + override fun beforeRunningAllComponentsOnElement(element: FirElement) { + beforeElementDiagnosticCollectionHandler?.beforeCollectingForElement(element) + } + override fun getCollectedDiagnostics(): List> { // Not necessary in IDE return emptyList() 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 new file mode 100644 index 00000000000..a75a638b4c5 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/BeforeElementDiagnosticCollectionHandler.kt @@ -0,0 +1,16 @@ +/* + * 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.diagnostics + +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.FirSessionComponent + +abstract class BeforeElementDiagnosticCollectionHandler: FirSessionComponent { + abstract fun beforeCollectingForElement(element: FirElement) +} + +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/sessions/FirIdeSessionFactory.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionFactory.kt index 5967462492d..d6d5e0881ba 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionFactory.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionFactory.kt @@ -58,6 +58,7 @@ internal object FirIdeSessionFactory { isRootModule: Boolean, librariesCache: LibrariesCache, languageVersionSettings: LanguageVersionSettings = LanguageVersionSettingsImpl.DEFAULT, + configureSession: (FirIdeSession.() -> Unit)? = null ): FirIdeSourcesSession { sessionsCache[moduleInfo]?.let { return it } val scopeProvider = KotlinScopeProvider(::wrapScopeWithJvmMapped) @@ -108,7 +109,8 @@ internal object FirIdeSessionFactory { project, builtinsAndCloneableSession, builtinTypes, - librariesCache + librariesCache, + configureSession = configureSession, ).symbolProvider ) dependentModules @@ -123,6 +125,7 @@ internal object FirIdeSessionFactory { sessionsCache, isRootModule = false, librariesCache, + configureSession = configureSession, ).symbolProvider } } @@ -137,6 +140,8 @@ internal object FirIdeSessionFactory { registerExtendedCommonCheckers() } }.configure() + + configureSession?.invoke(this) } } @@ -147,6 +152,7 @@ internal object FirIdeSessionFactory { builtinTypes: BuiltinTypes, librariesCache: LibrariesCache, languageVersionSettings: LanguageVersionSettings = LanguageVersionSettingsImpl.DEFAULT, + configureSession: (FirIdeSession.() -> Unit)?, ): FirIdeLibrariesSession = librariesCache.cached(moduleInfo) { checkCanceled() val searchScope = ModuleLibrariesSearchScope(moduleInfo.module) @@ -191,13 +197,15 @@ internal object FirIdeSessionFactory { ) ) register(FirJvmTypeMapper::class, FirJvmTypeMapper(this)) + configureSession?.invoke(this) } } fun createBuiltinsAndCloneableSession( project: Project, builtinTypes: BuiltinTypes, - languageVersionSettings: LanguageVersionSettings = LanguageVersionSettingsImpl.DEFAULT + languageVersionSettings: LanguageVersionSettings = LanguageVersionSettingsImpl.DEFAULT, + configureSession: (FirIdeSession.() -> Unit)? = null, ): FirIdeBuiltinsAndCloneableSession { return FirIdeBuiltinsAndCloneableSession(project, builtinTypes).apply { registerIdeComponents() @@ -215,6 +223,7 @@ internal object FirIdeSessionFactory { ) ) register(FirJvmTypeMapper::class, FirJvmTypeMapper(this)) + configureSession?.invoke(this) } } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt index 0c0d62c6e9e..a358f13412f 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt @@ -26,7 +26,10 @@ internal class FirIdeSessionProviderStorage(private val project: Project) { private val librariesCache by cachedValue(project, LibraryModificationTracker.getInstance(project)) { LibrariesCache() } - fun getSessionProvider(rootModule: ModuleSourceInfo): FirIdeSessionProvider { + fun getSessionProvider( + rootModule: ModuleSourceInfo, + configureSession: (FirIdeSession.() -> Unit)? = null + ): FirIdeSessionProvider { val firPhaseRunner = FirPhaseRunner() val builtinTypes = BuiltinTypes() @@ -45,6 +48,7 @@ internal class FirIdeSessionProviderStorage(private val project: Project) { sessions, isRootModule = true, librariesCache, + configureSession = configureSession, ) } sessions to session diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/constructor.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/constructor.kt new file mode 100644 index 00000000000..3b380befcfd --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/constructor.kt @@ -0,0 +1,3 @@ +fun foo(f: Float.(Int, String) -> Boolean) { + +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/functionalType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/functionalType.kt new file mode 100644 index 00000000000..d21912cc19d --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/functionalType.kt @@ -0,0 +1,3 @@ +class A(val x: Int = 10, val b: String) { + +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localFunctionWithImplicitType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localFunctionWithImplicitType.kt new file mode 100644 index 00000000000..93eeabeb1c4 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localFunctionWithImplicitType.kt @@ -0,0 +1,3 @@ +fun foo() { + fun local() = 0 +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localUnitFunction.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localUnitFunction.kt new file mode 100644 index 00000000000..a437874f557 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localUnitFunction.kt @@ -0,0 +1,5 @@ +fun foo() { + fun local() { + println("local") + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberFunctions.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberFunctions.kt new file mode 100644 index 00000000000..3e38d65f361 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberFunctions.kt @@ -0,0 +1,7 @@ +class A { + fun foo1() = 10 + + fun foo2() { + + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberProperties.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberProperties.kt new file mode 100644 index 00000000000..3e38d65f361 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberProperties.kt @@ -0,0 +1,7 @@ +class A { + fun foo1() = 10 + + fun foo2() { + + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelClasses.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelClasses.kt new file mode 100644 index 00000000000..016f11f0bce --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelClasses.kt @@ -0,0 +1,15 @@ +class A { + +} + +class B { + +} + +class C { + +} + +class D { + +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelFunctionsWithImplicitTypes.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelFunctionsWithImplicitTypes.kt new file mode 100644 index 00000000000..6d15d9153df --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelFunctionsWithImplicitTypes.kt @@ -0,0 +1,7 @@ +fun foo1() = 1 + +fun foo2() = 2 + +fun foo3() = 3 + +fun foo4() = 4 \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelUnitFunctions.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelUnitFunctions.kt new file mode 100644 index 00000000000..6737fbcf302 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelUnitFunctions.kt @@ -0,0 +1,15 @@ +fun foo1() { + println("foo1") +} + +fun foo2() { + println("foo2") +} + +fun foo3() { + println("foo3") +} + +fun foo4() { + println("foo4") +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/nestedClases.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/nestedClases.kt new file mode 100644 index 00000000000..0534a52714e --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/nestedClases.kt @@ -0,0 +1,11 @@ +class A { + class B { + + } + + object C { + class D { + + } + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/propertyWithGetterAndSetter.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/propertyWithGetterAndSetter.kt new file mode 100644 index 00000000000..e3481db3d06 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/propertyWithGetterAndSetter.kt @@ -0,0 +1,5 @@ +var withGetterAndSetter: Int = 42 + get() = field + set(value) { + field = value + } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/secondaryConstructor.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/secondaryConstructor.kt new file mode 100644 index 00000000000..b3fdb9dfa92 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/secondaryConstructor.kt @@ -0,0 +1,3 @@ +class A(val x: Int = 10, val b: String) { + constructor(i: Int) : this(x = 1, b = i.toString()) +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/AbstractDiagnosticTraversalCounterTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/AbstractDiagnosticTraversalCounterTest.kt new file mode 100644 index 00000000000..6588352b2be --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/AbstractDiagnosticTraversalCounterTest.kt @@ -0,0 +1,112 @@ +/* + * 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 org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.SessionConfiguration +import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid +import org.jetbrains.kotlin.idea.caches.project.getModuleInfo +import org.jetbrains.kotlin.idea.fir.low.level.api.api.DiagnosticCheckerFilter +import org.jetbrains.kotlin.idea.fir.low.level.api.api.collectDiagnosticsForFile +import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFir +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.renderWithClassName +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.psi.KtFile +import java.io.File + +/** + * Check that every declaration is visited exactly one time during diagnostic collection + */ +abstract class AbstractDiagnosticTraversalCounterTest : KotlinLightCodeInsightFixtureTestCase() { + override fun isFirPlugin(): Boolean = true + + 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) + } + + // we should get diagnostics before we resolve the whole file by ktFile.getOrBuildFir + ktFile.collectDiagnosticsForFile(resolveState, DiagnosticCheckerFilter.ONLY_COMMON_CHECKERS) + + val firFile = ktFile.getOrBuildFir(resolveState) + + val errorElements = collectErrorElements(firFile, handler) + + if (errorElements.isNotEmpty()) { + val zeroElements = errorElements.filter { it.second == 0 } + val nonZeroElements = errorElements.filter { it.second > 1 } + val message = buildString { + if (zeroElements.isNotEmpty()) { + appendLine( + """ |The following elements were not visited + |${zeroElements.joinToString(separator = "\n\n") { it.first.renderWithClassName() }} + """.trimMargin() + ) + } + if (nonZeroElements.isNotEmpty()) { + appendLine( + """ |The following elements were visited more than one time + |${nonZeroElements.joinToString(separator = "\n\n") { it.second.toString() + " times " + it.first.renderWithClassName() }} + """.trimMargin() + ) + } + } + fail(message) + } + } + + private fun collectErrorElements( + firFile: FirElement, + handler: BeforeElementTestDiagnosticCollectionHandler + ): List> { + val errorElements = mutableListOf>() + val nonDuplicatingElements = findNonDuplicatingFirElements(firFile) + firFile.accept(object : FirVisitorVoid() { + override fun visitElement(element: FirElement) { + if (element !in nonDuplicatingElements) return + val visitedTimes = handler.visitedTimes[element] ?: 0 + if (visitedTimes != 1) { + errorElements += element to visitedTimes + } + element.acceptChildren(this) + } + }) + return errorElements + } + + private fun findNonDuplicatingFirElements( + firFile: FirElement, + ): Set { + val elementUsageCount = mutableMapOf() + firFile.accept(object : FirVisitorVoid() { + override fun visitElement(element: FirElement) { + elementUsageCount.compute(element) { _, count -> (count ?: 0) + 1 } + } + }) + return elementUsageCount.filterValues { it == 1 }.keys + } + + + class BeforeElementTestDiagnosticCollectionHandler : BeforeElementDiagnosticCollectionHandler() { + val visitedTimes = mutableMapOf() + override fun beforeCollectingForElement(element: FirElement) { + if (!visitedTimes.containsKey(element)) { + visitedTimes[element] = 1 + } else { + visitedTimes.compute(element) { _, count -> (count ?: 0) + 1 } + } + } + } +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/DiagnosticTraversalCounterTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/DiagnosticTraversalCounterTestGenerated.java new file mode 100644 index 00000000000..0970f7b32e0 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/DiagnosticTraversalCounterTestGenerated.java @@ -0,0 +1,91 @@ +/* + * 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/diagnosticTraversalCounter") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class DiagnosticTraversalCounterTestGenerated extends AbstractDiagnosticTraversalCounterTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInDiagnosticTraversalCounter() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("constructor.kt") + public void testConstructor() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/constructor.kt"); + } + + @TestMetadata("functionalType.kt") + public void testFunctionalType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/functionalType.kt"); + } + + @TestMetadata("localFunctionWithImplicitType.kt") + public void testLocalFunctionWithImplicitType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localFunctionWithImplicitType.kt"); + } + + @TestMetadata("localUnitFunction.kt") + public void testLocalUnitFunction() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/localUnitFunction.kt"); + } + + @TestMetadata("memberFunctions.kt") + public void testMemberFunctions() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberFunctions.kt"); + } + + @TestMetadata("memberProperties.kt") + public void testMemberProperties() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/memberProperties.kt"); + } + + @TestMetadata("multipleTopLevelClasses.kt") + public void testMultipleTopLevelClasses() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelClasses.kt"); + } + + @TestMetadata("multipleTopLevelFunctionsWithImplicitTypes.kt") + public void testMultipleTopLevelFunctionsWithImplicitTypes() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelFunctionsWithImplicitTypes.kt"); + } + + @TestMetadata("multipleTopLevelUnitFunctions.kt") + public void testMultipleTopLevelUnitFunctions() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/multipleTopLevelUnitFunctions.kt"); + } + + @TestMetadata("nestedClases.kt") + public void testNestedClases() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/nestedClases.kt"); + } + + @TestMetadata("propertyWithGetterAndSetter.kt") + public void testPropertyWithGetterAndSetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/propertyWithGetterAndSetter.kt"); + } + + @TestMetadata("secondaryConstructor.kt") + public void testSecondaryConstructor() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/diagnosticTraversalCounter/secondaryConstructor.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 25cf2103fcc..fec79bff1cf 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 @@ -9,6 +9,9 @@ import com.intellij.openapi.components.service import com.intellij.openapi.module.Module 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.render 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 @@ -21,6 +24,9 @@ inline fun resolveWithClearCaches(context: KtElement, action: (FirModuleResolveS action(resolveState) } +internal fun FirElement.renderWithClassName(renderMode: FirRenderer.RenderMode = FirRenderer.RenderMode.Normal): String = + "${this::class.simpleName} `${render(renderMode)}`" + internal fun Module.incModificationTracker() { project.service().increaseModificationCountForModule(this) } \ No newline at end of file