[LL API] Filter duplicating compiled callables inside FirSymbolProvider
Before the code fragment analysis was extracted to the separate module, it was impossible to modify the symbol provider, so a conflict resolver was patched instead. The solution was not complete, though, as it only covered call resolution ambiguities. The compiler sometime calls a symbol provider directly, like it's done in 'BuiltinSymbolsBase'. Relevant tests are in the IntelliJ project (see singleBreakpoint/ conflictingStdlib.kt). ^KTIJ-27329 Fixed
This commit is contained in:
+53
-45
@@ -3,45 +3,48 @@
|
||||
* 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.resolver
|
||||
package org.jetbrains.kotlin.analysis.low.level.api.fir.providers
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.fir.psi
|
||||
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.ConeCallConflictResolver
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.ConeCallConflictResolverFactory
|
||||
import org.jetbrains.kotlin.fir.resolve.inference.InferenceComponents
|
||||
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProviderInternals
|
||||
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.CallableId
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator
|
||||
|
||||
internal class LLLibraryScopeAwareCallConflictResolverFactory(
|
||||
private val delegateFactory: ConeCallConflictResolverFactory
|
||||
) : ConeCallConflictResolverFactory() {
|
||||
override fun create(
|
||||
typeSpecificityComparator: TypeSpecificityComparator,
|
||||
components: InferenceComponents,
|
||||
transformerComponents: BodyResolveComponents,
|
||||
): ConeCallConflictResolver {
|
||||
val delegate = delegateFactory.create(typeSpecificityComparator, components, transformerComponents)
|
||||
return LLLibraryScopeAwareConeCallConflictResolver(delegate, transformerComponents)
|
||||
class LLFirCodeFragmentDependenciesSymbolProvider(private val delegate: FirSymbolProvider) : FirSymbolProvider(delegate.session) {
|
||||
override val symbolNamesProvider: FirSymbolNamesProvider
|
||||
get() = delegate.symbolNamesProvider
|
||||
|
||||
override fun getClassLikeSymbolByClassId(classId: ClassId): FirClassLikeSymbol<*>? {
|
||||
return delegate.getClassLikeSymbolByClassId(classId)
|
||||
}
|
||||
}
|
||||
|
||||
private class LLLibraryScopeAwareConeCallConflictResolver(
|
||||
private val delegate: ConeCallConflictResolver,
|
||||
private val bodyResolveComponents: BodyResolveComponents
|
||||
) : ConeCallConflictResolver() {
|
||||
override fun chooseMaximallySpecificCandidates(candidates: Set<Candidate>, discriminateAbstracts: Boolean): Set<Candidate> {
|
||||
val filteredCandidates = when {
|
||||
candidates.size > 1 -> filterCodeFragmentCandidates(candidates)
|
||||
else -> candidates
|
||||
}
|
||||
@FirSymbolProviderInternals
|
||||
override fun getTopLevelCallableSymbolsTo(destination: MutableList<FirCallableSymbol<*>>, packageFqName: FqName, name: Name) {
|
||||
destination += delegate.getTopLevelCallableSymbols(packageFqName, name).let(::filterSymbols)
|
||||
}
|
||||
|
||||
return delegate.chooseMaximallySpecificCandidates(filteredCandidates, discriminateAbstracts)
|
||||
@FirSymbolProviderInternals
|
||||
override fun getTopLevelFunctionSymbolsTo(destination: MutableList<FirNamedFunctionSymbol>, packageFqName: FqName, name: Name) {
|
||||
destination += delegate.getTopLevelFunctionSymbols(packageFqName, name).let(::filterSymbols)
|
||||
}
|
||||
|
||||
@FirSymbolProviderInternals
|
||||
override fun getTopLevelPropertySymbolsTo(destination: MutableList<FirPropertySymbol>, packageFqName: FqName, name: Name) {
|
||||
destination += delegate.getTopLevelPropertySymbols(packageFqName, name).let(::filterSymbols)
|
||||
}
|
||||
|
||||
override fun getPackage(fqName: FqName): FqName? {
|
||||
return delegate.getPackage(fqName)
|
||||
}
|
||||
|
||||
// In complex projects, there might be several library copies (with the same or different versions).
|
||||
@@ -50,18 +53,20 @@ private class LLLibraryScopeAwareConeCallConflictResolver(
|
||||
// Normally, K2 issues a 'resolution ambiguity' error on calls to such libraries. It is sort of acceptable for resolution, as
|
||||
// resolution errors are never shown in the library code. However, the backend, to which 'evaluate expression' needs to pass FIR
|
||||
// afterwards, is not designed for compiling ambiguous (and non-completed) calls.
|
||||
// The code below scans for declaration duplicates, and chooses a set of them from a single class input (a JAR or a directory).
|
||||
// The code below scans for declaration duplicates, and chooses a number of them from a single class input (a JAR or a directory).
|
||||
// The logic is not ideal, as, in theory, versions might differ non-trivially: each artifact may have unique declarations.
|
||||
// However, such cases should be relatively rare, and to provide the candidate list more precisely, one would need to also compare
|
||||
// signatures of each declaration.
|
||||
private fun filterCodeFragmentCandidates(candidates: Set<Candidate>): Set<Candidate> {
|
||||
val binaryCallableCandidates = LinkedHashMap<CallableId, MutableMap<VirtualFile, MutableList<Candidate>>>()
|
||||
val otherCandidates = ArrayList<Candidate>()
|
||||
private fun <T : FirCallableSymbol<*>> filterSymbols(symbols: List<T>): List<T> {
|
||||
if (symbols.size < 2) {
|
||||
return symbols
|
||||
}
|
||||
|
||||
for (candidate in candidates) {
|
||||
val symbol = candidate.symbol
|
||||
val binarySymbols = LinkedHashMap<CallableId, MutableMap<VirtualFile, MutableList<T>>>()
|
||||
val otherSymbols = ArrayList<T>()
|
||||
|
||||
if (symbol is FirCallableSymbol<*> && symbol.callableId.className == null) {
|
||||
for (symbol in symbols) {
|
||||
if (symbol.callableId.className == null) {
|
||||
val callableId = symbol.callableId
|
||||
|
||||
val symbolFile = symbol.fir.psi?.containingFile
|
||||
@@ -69,29 +74,32 @@ private class LLLibraryScopeAwareConeCallConflictResolver(
|
||||
if (symbolFile is KtFile && symbolFile.isCompiled && symbolVirtualFile != null) {
|
||||
val symbolRootVirtualFile = getSymbolRootFile(symbolVirtualFile, symbolFile.packageFqName)
|
||||
if (symbolRootVirtualFile != null) {
|
||||
binaryCallableCandidates
|
||||
binarySymbols
|
||||
.getOrPut(callableId, ::LinkedHashMap)
|
||||
.getOrPut(symbolRootVirtualFile, ::ArrayList)
|
||||
.add(candidate)
|
||||
.add(symbol)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
otherCandidates.add(candidate)
|
||||
otherSymbols.add(symbol)
|
||||
}
|
||||
|
||||
if (binaryCallableCandidates.isNotEmpty()) {
|
||||
return buildSet {
|
||||
addAll(otherCandidates)
|
||||
for (binaryCallableCandidateGroup in binaryCallableCandidates.values) {
|
||||
val chosenGroupKey = binaryCallableCandidateGroup.keys.maxBy { it.path }
|
||||
addAll(binaryCallableCandidateGroup[chosenGroupKey].orEmpty())
|
||||
if (binarySymbols.isNotEmpty()) {
|
||||
return buildList {
|
||||
addAll(otherSymbols)
|
||||
for (binarySymbolGroup in binarySymbols.values) {
|
||||
// For consistency with class symbol fetching, callable symbols are returned in the same order as indices returned.
|
||||
val firstBinarySymbolGroupValue = binarySymbolGroup.values.first()
|
||||
if (firstBinarySymbolGroupValue.isNotEmpty()) {
|
||||
addAll(firstBinarySymbolGroupValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates
|
||||
return symbols
|
||||
}
|
||||
|
||||
private fun getSymbolRootFile(virtualFile: VirtualFile, packageFqName: FqName): VirtualFile? {
|
||||
+5
-6
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirLazyDeclarationResol
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.*
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.*
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolver.LLLibraryScopeAwareCallConflictResolverFactory
|
||||
import org.jetbrains.kotlin.analysis.project.structure.*
|
||||
import org.jetbrains.kotlin.analysis.providers.KotlinAnchorModuleProvider
|
||||
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
|
||||
@@ -33,12 +32,11 @@ 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.languageVersionSettings
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.ConeCallConflictResolverFactory
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.callConflictResolverFactory
|
||||
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.dependenciesSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirCompositeSymbolProvider
|
||||
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
|
||||
@@ -494,11 +492,14 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
|
||||
registerCommonComponentsAfterExtensionsAreConfigured()
|
||||
|
||||
val dependencyProvider = LLFirDependenciesSymbolProvider(this) {
|
||||
buildList {
|
||||
val providers = buildList {
|
||||
addMerged(computeFlattenedSymbolProviders(listOf(contextSession)))
|
||||
add(contextSession.dependenciesSymbolProvider) // Add the context module dependency symbol provider as is
|
||||
add(builtinsSession.symbolProvider)
|
||||
}
|
||||
|
||||
// Wrap dependencies into a single classpath-filtering provider
|
||||
listOf(LLFirCodeFragmentDependenciesSymbolProvider(FirCompositeSymbolProvider(session, providers)))
|
||||
}
|
||||
|
||||
register(DEPENDENCIES_SYMBOL_PROVIDER_QUALIFIED_KEY, dependencyProvider)
|
||||
@@ -533,8 +534,6 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
|
||||
)
|
||||
|
||||
register(FirJvmTypeMapper::class, FirJvmTypeMapper(this))
|
||||
|
||||
register(ConeCallConflictResolverFactory::class, LLLibraryScopeAwareCallConflictResolverFactory(callConflictResolverFactory))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user