[Analysis API] Add API for code compilation

The API replaces 'KotlinCompilerIde' in the IntelliJ IDEA plugin.
Code moved to Analysis API as its K2 implementation severely depends on
the internal compiler API (FIR).

The API is going to be used in the JVM debugger evaluator, and in
the Bytecode Tool Window.

In this commit, only ordinary Kotlin are supported.
In later commits, there will be also support for 'KtCodeFragment' files,
as well as some test coverage.
This commit is contained in:
Yan Zhulanow
2023-07-05 22:45:54 +09:00
committed by Space Team
parent 441735c2a8
commit 0f6f22d76b
29 changed files with 1103 additions and 4 deletions
+2 -1
View File
@@ -13,9 +13,10 @@ dependencies {
implementation(project(":analysis:kt-references:kt-references-fe10"))
implementation(project(":compiler:backend"))
implementation(project(":compiler:backend.jvm"))
implementation(project(":compiler:backend-common"))
implementation(project(":compiler:backend.common.jvm"))
implementation(project(":compiler:backend.jvm"))
implementation(project(":compiler:backend.jvm.entrypoint"))
testApiJUnit5()
testImplementation(project(":analysis:analysis-api-providers"))
@@ -59,7 +59,8 @@ class CliFe10AnalysisFacade : Fe10AnalysisFacade {
return getHandler(element).kotlinTypeRefiner ?: error("Resolution is not performed")
}
override fun analyze(element: KtElement, mode: Fe10AnalysisFacade.AnalysisMode): BindingContext {
override fun analyze(elements: List<KtElement>, mode: Fe10AnalysisFacade.AnalysisMode): BindingContext {
val element = elements.firstOrNull() ?: return BindingContext.EMPTY
return getResolveSession(element).bindingContext
}
@@ -35,7 +35,11 @@ interface Fe10AnalysisFacade {
fun getOverloadingConflictResolver(element: KtElement): OverloadingConflictResolver<ResolvedCall<*>>
fun getKotlinTypeRefiner(element: KtElement): KotlinTypeRefiner
fun analyze(element: KtElement, mode: AnalysisMode = AnalysisMode.FULL): BindingContext
fun analyze(elements: List<KtElement>, mode: AnalysisMode = AnalysisMode.FULL): BindingContext
fun analyze(element: KtElement, mode: AnalysisMode = AnalysisMode.FULL): BindingContext {
return analyze(listOf(element), mode)
}
fun getOrigin(file: VirtualFile): KtSymbolOrigin
@@ -70,6 +70,7 @@ class KtFe10AnalysisSession(
override val substitutorFactoryImpl: KtSubstitutorFactory = KtFe10SubstitutorFactory(this)
override val symbolProviderByJavaPsiImpl: KtSymbolProviderByJavaPsi = KtFe10SymbolProviderByJavaPsi(this)
override val resolveExtensionInfoProviderImpl: KtResolveExtensionInfoProvider = KtFe10ResolveExtensionInfoProvider(this)
override val compilerFacilityImpl: KtCompilerFacility = KtFe10CompilerFacility(this)
override fun createContextDependentCopy(originalKtFile: KtFile, elementToReanalyze: KtElement): KtAnalysisSession =
withValidityAssertion {
@@ -0,0 +1,197 @@
/*
* 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.api.descriptors.components
import org.jetbrains.kotlin.analysis.api.components.KtCompilerFacility
import org.jetbrains.kotlin.analysis.api.components.KtCompilationResult
import org.jetbrains.kotlin.analysis.api.components.KtCompilerTarget
import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisFacade.AnalysisMode
import org.jetbrains.kotlin.analysis.api.descriptors.KtFe10AnalysisSession
import org.jetbrains.kotlin.analysis.api.descriptors.components.base.Fe10KtAnalysisSessionComponent
import org.jetbrains.kotlin.analysis.api.descriptors.utils.InlineFunctionAnalyzer
import org.jetbrains.kotlin.analysis.api.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.analysis.api.impl.base.util.KtCompiledFileForOutputFile
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.jvm.FacadeClassSourceShimForFragmentCompilation
import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl
import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory
import org.jetbrains.kotlin.backend.jvm.jvmPhases
import org.jetbrains.kotlin.codegen.DefaultCodegenFactory
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.load.kotlin.toSourceElement
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource
/**
* Whether unbound IR symbols should be stubbed instead of linked.
* This should be enabled if the compiled file could refer to symbols defined in another file of the same module.
* Such symbols are not compiled (only the file is passed to the backend) and so they cannot be linked from a dependency.
*
* The option only has an effect when the IR backend is active.
*/
val STUB_UNBOUND_IR_SYMBOLS: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey<Boolean>("stub unbound IR symbols")
internal class KtFe10CompilerFacility(
override val analysisSession: KtFe10AnalysisSession
) : KtCompilerFacility(), Fe10KtAnalysisSessionComponent {
override fun compile(file: KtFile, configuration: CompilerConfiguration, target: KtCompilerTarget): KtCompilationResult {
if (file is KtCodeFragment) {
throw UnsupportedOperationException("Code fragments are not supported in K1 implementation")
}
val classBuilderFactory = when (target) {
is KtCompilerTarget.Jvm -> target.classBuilderFactory
}
val effectiveConfiguration = configuration
.copy()
.apply {
put(JVMConfigurationKeys.DO_NOT_CLEAR_BINDING_CONTEXT, true)
}
val useIrBackend = effectiveConfiguration.getBoolean(JVMConfigurationKeys.IR)
val disableInline = effectiveConfiguration.getBoolean(CommonConfigurationKeys.DISABLE_INLINE)
// The binding context needs to be built from all files with reachable inline functions, as such files may contain classes whose
// descriptors must be available in the binding context for the IR backend. Note that the full bytecode is only generated for
// `file` because of filtering in `generateClassFilter`, while only select declarations from other files are generated if needed
// by the backend.
val inlineAnalyzer = InlineFunctionAnalyzer(analysisContext, analyzeOnlyReifiedInlineFunctions = disableInline)
inlineAnalyzer.analyze(file)
val filesToCompile = inlineAnalyzer.allFiles()
val bindingContext = analysisContext.analyze(filesToCompile, AnalysisMode.ALL_COMPILER_CHECKS)
val frontendErrors = computeErrors(bindingContext.diagnostics, effectiveConfiguration)
if (frontendErrors.isNotEmpty()) {
return KtCompilationResult.Failure(frontendErrors)
}
// The IR backend will try to regenerate object literals defined in inline functions from generated class files during inlining.
// Hence, we need to be aware of which object declarations are defined in the relevant inline functions.
val inlineObjectDeclarations = when {
useIrBackend -> inlineAnalyzer.inlineObjectDeclarations()
else -> setOf()
}
val inlineObjectDeclarationFiles = inlineObjectDeclarations.mapTo(mutableSetOf()) { it.containingKtFile }
class GenerateClassFilter : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean {
return file === ktFile || inlineObjectDeclarationFiles.contains(ktFile)
}
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean {
return true
}
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean {
return processingClassOrObject.containingKtFile === file ||
processingClassOrObject is KtObjectDeclaration && inlineObjectDeclarations.contains(processingClassOrObject)
}
override fun shouldGenerateScript(script: KtScript): Boolean {
return script.containingKtFile === file
}
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
}
val generateClassFilter = GenerateClassFilter()
val codegenFactory = when {
useIrBackend -> createJvmIrCodegenFactory(effectiveConfiguration)
else -> DefaultCodegenFactory
}
val state = GenerationState.Builder(
file.project,
classBuilderFactory,
analysisContext.resolveSession.moduleDescriptor,
bindingContext,
filesToCompile,
effectiveConfiguration,
).generateDeclaredClassFilter(generateClassFilter)
.codegenFactory(codegenFactory)
.build()
try {
KotlinCodegenFacade.compileCorrectFiles(state)
val backendErrors = computeErrors(state.collectedExtraJvmDiagnostics, effectiveConfiguration)
if (backendErrors.isNotEmpty()) {
return KtCompilationResult.Failure(backendErrors)
}
val outputFiles = state.factory.asList().map(::KtCompiledFileForOutputFile)
return KtCompilationResult.Success(outputFiles)
} finally {
state.destroy()
}
}
private fun computeErrors(diagnostics: Diagnostics, configuration: CompilerConfiguration): List<KtDiagnostic> {
val allowedErrorFilter = configuration[ALLOWED_ERROR_FILTER] ?: { true }
return buildList {
for (diagnostic in diagnostics.all()) {
if (diagnostic.severity == Severity.ERROR) {
val ktDiagnostic = KtFe10Diagnostic(diagnostic, token)
if (!allowedErrorFilter(ktDiagnostic)) {
add(ktDiagnostic)
}
}
}
}
}
private fun createJvmIrCodegenFactory(configuration: CompilerConfiguration): JvmIrCodegenFactory {
val stubUnboundIrSymbols = configuration[STUB_UNBOUND_IR_SYMBOLS] ?: false
val jvmGeneratorExtensions = if (stubUnboundIrSymbols) {
object : JvmGeneratorExtensionsImpl(configuration) {
override fun getContainerSource(descriptor: DeclarationDescriptor): DeserializedContainerSource? {
// Stubbed top-level function IR symbols (from other source files in the module) require a parent facade class to be
// generated, which requires a container source to be provided. Without a facade class, function IR symbols will have
// an `IrExternalPackageFragment` parent, which trips up code generation during IR lowering.
val psiSourceFile =
descriptor.toSourceElement.containingFile as? PsiSourceFile ?: return super.getContainerSource(descriptor)
return FacadeClassSourceShimForFragmentCompilation(psiSourceFile)
}
}
} else {
JvmGeneratorExtensionsImpl(configuration)
}
val ideCodegenSettings = JvmIrCodegenFactory.IdeCodegenSettings(
shouldStubAndNotLinkUnboundSymbols = stubUnboundIrSymbols,
shouldDeduplicateBuiltInSymbols = stubUnboundIrSymbols,
// Because the file to compile may be contained in a "common" multiplatform module, an `expect` declaration doesn't necessarily
// have an obvious associated `actual` symbol. `shouldStubOrphanedExpectSymbols` generates stubs for such `expect` declarations.
shouldStubOrphanedExpectSymbols = true,
// Likewise, the file to compile may be contained in a "platform" multiplatform module, where the `actual` declaration is
// referenced in the symbol table automatically, but not its `expect` counterpart, because it isn't contained in the files to
// compile. `shouldReferenceUndiscoveredExpectSymbols` references such `expect` symbols in the symbol table so that they can
// subsequently be stubbed.
shouldReferenceUndiscoveredExpectSymbols = true,
)
return JvmIrCodegenFactory(
configuration,
PhaseConfig(jvmPhases),
jvmGeneratorExtensions = jvmGeneratorExtensions,
ideCodegenSettings = ideCodegenSettings,
)
}
}
@@ -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.api.descriptors.utils
import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisContext
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.references.fe10.util.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor
class InlineFunctionAnalyzer(
private val analysisContext: Fe10AnalysisContext,
private val analyzeOnlyReifiedInlineFunctions: Boolean,
) {
private val analyzedElements: MutableSet<KtElement> = mutableSetOf()
private val inlineFunctionsWithBody: MutableSet<KtDeclarationWithBody> = mutableSetOf()
/**
* Collects all inline function calls in an [element] (usually a file) and follows each transitively.
*/
fun analyze(element: KtElement) {
val project = element.project
val nextInlineFunctions = HashSet<KtDeclarationWithBody>()
element.accept(object : KtTreeVisitorVoid() {
override fun visitExpression(expression: KtExpression) {
super.visitExpression(expression)
val bindingContext = analysisContext.analyze(expression)
val call = bindingContext.get(BindingContext.CALL, expression) ?: return
val resolvedCall = bindingContext.get(BindingContext.RESOLVED_CALL, call)
checkResolveCall(resolvedCall)
}
override fun visitDestructuringDeclaration(destructuringDeclaration: KtDestructuringDeclaration) {
super.visitDestructuringDeclaration(destructuringDeclaration)
val bindingContext = analysisContext.analyze(destructuringDeclaration)
for (entry in destructuringDeclaration.entries) {
val resolvedCall = bindingContext.get(BindingContext.COMPONENT_RESOLVED_CALL, entry)
checkResolveCall(resolvedCall)
}
}
override fun visitForExpression(expression: KtForExpression) {
super.visitForExpression(expression)
val bindingContext = analysisContext.analyze(expression)
checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL, expression.loopRange))
checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_HAS_NEXT_RESOLVED_CALL, expression.loopRange))
checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL, expression.loopRange))
}
private fun checkResolveCall(resolvedCall: ResolvedCall<*>?) {
if (resolvedCall == null) return
val descriptor = resolvedCall.resultingDescriptor
if (descriptor is DeserializedSimpleFunctionDescriptor) return
analyzeNextIfInline(descriptor)
if (descriptor is PropertyDescriptor) {
for (accessor in descriptor.accessors) {
analyzeNextIfInline(accessor)
}
}
}
private fun analyzeNextIfInline(descriptor: CallableDescriptor) {
if (!InlineUtil.isInline(descriptor) || analyzeOnlyReifiedInlineFunctions && !hasReifiedTypeParameters(descriptor)) {
return
}
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor)
if (declaration != null && declaration is KtDeclarationWithBody && !analyzedElements.contains(declaration)) {
nextInlineFunctions.add(declaration)
return
}
}
})
analyzedElements.add(element)
if (nextInlineFunctions.isNotEmpty()) {
for (inlineFunction in nextInlineFunctions) {
if (inlineFunction.bodyExpression != null) {
inlineFunctionsWithBody.add(inlineFunction)
analyze(inlineFunction)
}
}
analyzedElements.addAll(nextInlineFunctions)
}
}
private fun hasReifiedTypeParameters(descriptor: CallableDescriptor): Boolean {
return descriptor.typeParameters.any { it.isReified }
}
/**
* Returns the list of files that contain all reached inline functions.
*/
fun allFiles(): List<KtFile> = analyzedElements.mapTo(mutableSetOf()) { it.containingKtFile }.toList()
/**
* Returns the set of [KtObjectDeclaration]s which are defined as an object literal in one of the reached inline functions.
*/
fun inlineObjectDeclarations(): Set<KtObjectDeclaration> {
val results = mutableSetOf<KtObjectDeclaration>()
inlineFunctionsWithBody.forEach { inlineFunction ->
val body = inlineFunction.bodyExpression ?: return@forEach
body.accept(object : KtTreeVisitorVoid() {
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
super.visitObjectLiteralExpression(expression)
results.add(expression.objectDeclaration)
}
})
}
return results
}
}