[LL API] Fix code fragment compilation in library sources (KT-61383)
In complex projects, there might be several library copies (with the same or different versions). As there is no way to build a reliable dependency graph between libraries, a project library depends on all other libraries. As a result, there might be several declarations in the classpath with the same name and signature. Normally, K2 issues a 'resolution ambiguity' error on calls to such libraries. It is 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.
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.resolve.extensions
|
||||
|
||||
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.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private class LLLibraryScopeAwareConeCallConflictResolver(
|
||||
private val delegate: ConeCallConflictResolver,
|
||||
private val bodyResolveComponents: BodyResolveComponents
|
||||
) : ConeCallConflictResolver() {
|
||||
override fun chooseMaximallySpecificCandidates(candidates: Set<Candidate>, discriminateAbstracts: Boolean): Set<Candidate> {
|
||||
if (candidates.size > 1 && bodyResolveComponents.file.psi is KtCodeFragment) {
|
||||
val filteredCandidates = filterCodeFragmentCandidates(candidates)
|
||||
return delegate.chooseMaximallySpecificCandidates(filteredCandidates, discriminateAbstracts)
|
||||
}
|
||||
|
||||
return delegate.chooseMaximallySpecificCandidates(candidates, discriminateAbstracts)
|
||||
}
|
||||
|
||||
// In complex projects, there might be several library copies (with the same or different versions).
|
||||
// As there is no way to build a reliable dependency graph between libraries, a project library depends on all other libraries.
|
||||
// As a result, there might be several declarations in the classpath with the same name and signature.
|
||||
// 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 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>()
|
||||
|
||||
for (candidate in candidates) {
|
||||
val symbol = candidate.symbol
|
||||
|
||||
if (symbol is FirCallableSymbol<*> && symbol.callableId.className == null) {
|
||||
val callableId = symbol.callableId
|
||||
|
||||
val symbolFile = symbol.fir.psi?.containingFile
|
||||
val symbolVirtualFile = symbolFile?.virtualFile
|
||||
if (symbolFile is KtFile && symbolFile.isCompiled && symbolVirtualFile != null) {
|
||||
val symbolRootVirtualFile = getSymbolRootFile(symbolVirtualFile, symbolFile.packageFqName)
|
||||
if (symbolRootVirtualFile != null) {
|
||||
binaryCallableCandidates
|
||||
.getOrPut(callableId, ::LinkedHashMap)
|
||||
.getOrPut(symbolRootVirtualFile, ::ArrayList)
|
||||
.add(candidate)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
otherCandidates.add(candidate)
|
||||
}
|
||||
|
||||
if (binaryCallableCandidates.isNotEmpty()) {
|
||||
return buildSet {
|
||||
addAll(otherCandidates)
|
||||
for (binaryCallableCandidateGroup in binaryCallableCandidates.values) {
|
||||
val chosenGroupKey = binaryCallableCandidateGroup.keys.maxBy { it.path }
|
||||
addAll(binaryCallableCandidateGroup[chosenGroupKey].orEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
|
||||
private fun getSymbolRootFile(virtualFile: VirtualFile, packageFqName: FqName): VirtualFile? {
|
||||
val nestingLevel = packageFqName.pathSegments().size
|
||||
return generateSequence(virtualFile) { it.parent }.drop(nestingLevel + 1).firstOrNull()
|
||||
}
|
||||
}
|
||||
+10
@@ -9,14 +9,18 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
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.resolve.extensions.LLLibraryScopeAwareCallConflictResolverFactory
|
||||
import org.jetbrains.kotlin.analysis.project.structure.*
|
||||
import org.jetbrains.kotlin.analysis.providers.createPackagePartProvider
|
||||
import org.jetbrains.kotlin.fir.BuiltinTypes
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.SessionConfiguration
|
||||
import org.jetbrains.kotlin.fir.backend.jvm.FirJvmTypeMapper
|
||||
import org.jetbrains.kotlin.fir.deserialization.SingleModuleDataProvider
|
||||
import org.jetbrains.kotlin.fir.java.JavaSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.java.deserialization.OptionalAnnotationClassesProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.ConeCallConflictResolverFactory
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.callConflictResolverFactory
|
||||
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.scopes.wrapScopeWithJvmMapped
|
||||
import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider
|
||||
@@ -71,6 +75,7 @@ internal class LLFirJvmSessionFactory(project: Project) : LLFirAbstractSessionFa
|
||||
)
|
||||
register(JavaSymbolProvider::class, javaSymbolProvider)
|
||||
register(FirJvmTypeMapper::class, FirJvmTypeMapper(this))
|
||||
registerLibraryScopeAwareCallConflictResolverFactory()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +84,14 @@ internal class LLFirJvmSessionFactory(project: Project) : LLFirAbstractSessionFa
|
||||
registerCommonJavaComponents(JavaModuleResolver.getInstance(project))
|
||||
registerJavaSpecificResolveComponents()
|
||||
register(FirJvmTypeMapper::class, FirJvmTypeMapper(this))
|
||||
registerLibraryScopeAwareCallConflictResolverFactory()
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirSession.registerLibraryScopeAwareCallConflictResolverFactory() {
|
||||
register(ConeCallConflictResolverFactory::class, LLLibraryScopeAwareCallConflictResolverFactory(callConflictResolverFactory))
|
||||
}
|
||||
|
||||
override fun createProjectLibraryProvidersForScope(
|
||||
session: LLFirSession,
|
||||
moduleData: LLFirModuleData,
|
||||
|
||||
Reference in New Issue
Block a user