[LL FIR] Symbol provider for binary library FIR session
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirBinaryLibraryDependenciesSymbolProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirBinaryLibraryDependenciesSymbolProvider.kt
new file mode 100644
index 0000000..1c1aba8
--- /dev/null
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirBinaryLibraryDependenciesSymbolProvider.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2010-2023 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.analysis.low.level.api.fir.providers
+
+import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSessionCache
+import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
+import org.jetbrains.kotlin.fir.FirSession
+import org.jetbrains.kotlin.fir.resolve.providers.*
+import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+
+internal class LLFirBinaryLibraryDependenciesSymbolProvider(
+    val symbolProvider: LLFirModuleWithDependenciesSymbolProvider,
+    val module: KtLibraryModule,
+    session: FirSession
+) : FirSymbolProvider(session) {
+    val llFirSessionCache = LLFirSessionCache.getInstance(module.project)
+    val dependencies = buildSet {
+        addAll(module.directRegularDependencies)
+        addAll(module.transitiveDependsOnDependencies)
+    }
+    override val symbolNamesProvider: FirSymbolNamesProvider = FirNullSymbolNamesProvider
+
+    @OptIn(UnsafeCastFunction::class)
+    private fun <T> withVisited (
+        visitedModules: MutableSet<KtLibraryModule>,
+        body: FirSymbolProvider.() -> T,
+    ): T? {
+        if (module in visitedModules)
+            return null
+        visitedModules.add(module)
+        dependencies.minus(visitedModules).forEach {
+            val moduleSymbolProvider = llFirSessionCache.getSession(it, true).symbolProvider
+            val symbol = when(it) {
+                is KtLibraryModule -> {
+                    moduleSymbolProvider
+                        .safeAs<LLFirBinaryLibraryDependenciesSymbolProvider>()?.run {
+                            symbolProvider.body() ?: withVisited(visitedModules, body)
+                        }
+                }
+                else -> {
+                    moduleSymbolProvider.body()
+                }
+            }
+            if (symbol != null)
+                return symbol
+        }
+        return null
+    }
+
+    @OptIn(UnsafeCastFunction::class)
+    private fun withVisitedVoid(
+        visitedModules: MutableSet<KtLibraryModule>,
+        body: FirSymbolProvider.() -> Unit,
+    ) {
+        if (module in visitedModules)
+            return
+        visitedModules.add(module)
+        dependencies.minus(visitedModules).forEach {
+            val moduleSymbolProvider = llFirSessionCache.getSession(it, true).symbolProvider
+            when (it) {
+                is KtLibraryModule -> {
+                    moduleSymbolProvider
+                        .safeAs<LLFirBinaryLibraryDependenciesSymbolProvider>()?.run {
+                            symbolProvider.body()
+                            withVisited(visitedModules, body)
+                        }
+
+                }
+                else -> {
+                    moduleSymbolProvider.body()
+                }
+            }
+        }
+    }
+
+    override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? {
+        return symbolProvider.getClassLikeSymbolByClassId(classId) ?: withVisited(mutableSetOf()) {
+            getClassLikeSymbolByClassId(classId)
+        }
+    }
+
+    @FirSymbolProviderInternals
+    override fun getTopLevelCallableSymbolsTo(
+        destination: MutableList<FirCallableSymbol<*>>,
+        packageFqName: FqName,
+        name: Name,
+    ) {
+        symbolProvider.getTopLevelCallableSymbolsTo(destination, packageFqName, name)
+        withVisitedVoid(mutableSetOf()) {
+            getTopLevelCallableSymbolsTo(destination, packageFqName, name)
+        }
+    }
+
+    @FirSymbolProviderInternals
+    override fun getTopLevelFunctionSymbolsTo(
+        destination: MutableList<FirNamedFunctionSymbol>,
+        packageFqName: FqName,
+        name: Name,
+    ) {
+        symbolProvider.getTopLevelFunctionSymbolsTo(destination, packageFqName, name)
+        withVisitedVoid(mutableSetOf()) {
+            getTopLevelFunctionSymbolsTo(destination, packageFqName, name)
+        }
+    }
+
+    @FirSymbolProviderInternals
+    override fun getTopLevelPropertySymbolsTo(destination: MutableList<FirPropertySymbol>, packageFqName: FqName, name: Name) {
+        symbolProvider.getTopLevelPropertySymbolsTo(destination, packageFqName, name)
+        withVisitedVoid(mutableSetOf()) {
+            getTopLevelPropertySymbolsTo(destination, packageFqName, name)
+        }
+    }
+
+    override fun getPackage(fqName: FqName): FqName? {
+        return symbolProvider.getPackage(fqName) ?: withVisited(mutableSetOf()) {
+            getPackage(fqName)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirAbstractSessionFactory.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirAbstractSessionFactory.kt
index c9d1367..5f09657 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirAbstractSessionFactory.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirAbstractSessionFactory.kt
@@ -32,11 +32,8 @@
 import org.jetbrains.kotlin.fir.backend.jvm.FirJvmTypeMapper
 import org.jetbrains.kotlin.fir.extensions.*
 import org.jetbrains.kotlin.fir.java.JavaSymbolProvider
-import org.jetbrains.kotlin.fir.resolve.providers.DEPENDENCIES_SYMBOL_PROVIDER_QUALIFIED_KEY
-import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
-import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
+import org.jetbrains.kotlin.fir.resolve.providers.*
 import org.jetbrains.kotlin.fir.resolve.providers.impl.FirExtensionSyntheticFunctionInterfaceProvider
-import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
 import org.jetbrains.kotlin.fir.resolve.scopes.wrapScopeWithJvmMapped
 import org.jetbrains.kotlin.fir.resolve.transformers.FirDummyCompilerLazyDeclarationResolver
 import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider
@@ -412,7 +409,12 @@
                 builtinTypes,
                 module.contentScope,
                 builtinsSession.symbolProvider
-            )
+            ).let {
+                if (module is KtLibraryModule)
+                    LLFirBinaryLibraryDependenciesSymbolProvider(it, module, session)
+                else
+                    it
+            }
 
             register(LLFirFirClassByPsiClassProvider::class, LLFirFirClassByPsiClassProvider(this))
             register(FirProvider::class, LLFirLibrarySessionProvider(symbolProvider))
diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionCache.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionCache.kt
index b47ff24..c9bf214 100644
--- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionCache.kt
+++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionCache.kt
@@ -77,8 +77,8 @@
         val sessionFactory = createPlatformAwareSessionFactory(module)
         return when (module) {
             is KtSourceModule -> sessionFactory.createSourcesSession(module)
-            is KtLibraryModule, is KtLibrarySourceModule -> sessionFactory.createLibrarySession(module)
-            is KtSdkModule -> sessionFactory.createBinaryLibrarySession(module)
+            is KtLibrarySourceModule -> sessionFactory.createLibrarySession(module)
+            is KtLibraryModule, is KtSdkModule -> sessionFactory.createBinaryLibrarySession(module as KtBinaryModule)
             is KtScriptModule -> sessionFactory.createScriptSession(module)
             is KtNotUnderContentRootModule -> sessionFactory.createNotUnderContentRootResolvableSession(module)
             else -> error("Unexpected module kind: ${module::class.simpleName}")