[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:
@@ -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"))
|
||||
|
||||
+2
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
|
||||
+1
@@ -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 {
|
||||
|
||||
+197
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
+131
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,11 @@ dependencies {
|
||||
api(project(":analysis:analysis-api-impl-base"))
|
||||
api(project(":analysis:light-classes-base"))
|
||||
api(project(":compiler:backend.common.jvm"))
|
||||
implementation(project(":compiler:cli-base"))
|
||||
implementation(project(":compiler:backend"))
|
||||
implementation(project(":compiler:backend.jvm.entrypoint"))
|
||||
implementation(project(":compiler:ir.backend.common"))
|
||||
implementation(project(":compiler:ir.serialization.jvm"))
|
||||
api(intellijCore())
|
||||
implementation(project(":analysis:analysis-api-providers"))
|
||||
implementation(project(":analysis:analysis-internal-utils"))
|
||||
|
||||
+2
@@ -128,6 +128,8 @@ private constructor(
|
||||
|
||||
override val resolveExtensionInfoProviderImpl: KtResolveExtensionInfoProvider = KtFirResolveExtensionInfoProvider(this)
|
||||
|
||||
override val compilerFacilityImpl: KtCompilerFacility = KtFirCompilerFacility(this)
|
||||
|
||||
@Suppress("AnalysisApiMissingLifetimeCheck")
|
||||
override fun createContextDependentCopy(originalKtFile: KtFile, elementToReanalyze: KtElement): KtAnalysisSession {
|
||||
check(mode == AnalysisSessionMode.REGULAR) {
|
||||
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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.fir.components
|
||||
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import org.jetbrains.kotlin.analysis.api.components.KtCompilationResult
|
||||
import org.jetbrains.kotlin.analysis.api.components.KtCompilerFacility
|
||||
import org.jetbrains.kotlin.analysis.api.components.KtCompilerTarget
|
||||
import org.jetbrains.kotlin.analysis.api.diagnostics.KtDiagnostic
|
||||
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.impl.base.util.KtCompiledFileForOutputFile
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.DiagnosticCheckerFilter
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDiagnosticsForFile
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirFile
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirWholeFileResolveTarget
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.resolve
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.CompilationPeerCollector
|
||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||
import org.jetbrains.kotlin.backend.jvm.*
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
|
||||
import org.jetbrains.kotlin.codegen.CodegenFactory
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.constant.EvaluatedConstTracker
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticMarker
|
||||
import org.jetbrains.kotlin.diagnostics.KtPsiDiagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.fir.backend.Fir2IrCommonMemberStorage
|
||||
import org.jetbrains.kotlin.fir.backend.Fir2IrConfiguration
|
||||
import org.jetbrains.kotlin.fir.backend.Fir2IrConverter
|
||||
import org.jetbrains.kotlin.fir.backend.jvm.*
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
||||
import org.jetbrains.kotlin.fir.pipeline.signatureComposerForJvmFir2Ir
|
||||
import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmIrMangler
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
||||
import org.jetbrains.kotlin.load.kotlin.toSourceElement
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource
|
||||
|
||||
internal class KtFirCompilerFacility(
|
||||
override val analysisSession: KtFirAnalysisSession
|
||||
) : KtCompilerFacility(), KtFirAnalysisSessionComponent {
|
||||
override fun compile(file: KtFile, configuration: CompilerConfiguration, target: KtCompilerTarget): KtCompilationResult {
|
||||
val classBuilderFactory = when (target) {
|
||||
is KtCompilerTarget.Jvm -> target.classBuilderFactory
|
||||
}
|
||||
|
||||
val effectiveConfiguration = configuration
|
||||
.copy()
|
||||
.apply {
|
||||
put(CommonConfigurationKeys.USE_FIR, true)
|
||||
}
|
||||
|
||||
val mainFirFile = getFullyResolvedFirFile(file)
|
||||
|
||||
val frontendDiagnostics = file.collectDiagnosticsForFile(firResolveSession, DiagnosticCheckerFilter.ONLY_COMMON_CHECKERS)
|
||||
val frontendErrors = computeErrors(frontendDiagnostics, effectiveConfiguration)
|
||||
|
||||
if (frontendErrors.isNotEmpty()) {
|
||||
return KtCompilationResult.Failure(frontendErrors)
|
||||
}
|
||||
|
||||
val compilationPeerData = CompilationPeerCollector.process(mainFirFile)
|
||||
|
||||
val filesToCompile = compilationPeerData.files
|
||||
|
||||
val firFilesToCompile = filesToCompile.map(::getFullyResolvedFirFile)
|
||||
|
||||
val generateClassFilter = SingleFileGenerateClassFilter(file, compilationPeerData.inlinedClasses)
|
||||
val fir2IrExtensions = JvmFir2IrExtensions(effectiveConfiguration, JvmIrDeserializerImpl(), JvmIrMangler)
|
||||
|
||||
val fir2IrConfiguration = Fir2IrConfiguration(
|
||||
effectiveConfiguration.languageVersionSettings,
|
||||
linkViaSignatures = false,
|
||||
effectiveConfiguration[CommonConfigurationKeys.EVALUATED_CONST_TRACKER] ?: EvaluatedConstTracker.create(),
|
||||
effectiveConfiguration[CommonConfigurationKeys.INLINE_CONST_TRACKER]
|
||||
)
|
||||
|
||||
val fir2IrResult = Fir2IrConverter.createModuleFragmentWithSignaturesIfNeeded(
|
||||
rootModuleSession,
|
||||
firResolveSession.getScopeSessionFor(rootModuleSession),
|
||||
firFilesToCompile,
|
||||
fir2IrExtensions,
|
||||
fir2IrConfiguration,
|
||||
JvmIrMangler,
|
||||
IrFactoryImpl,
|
||||
FirJvmVisibilityConverter,
|
||||
Fir2IrJvmSpecialAnnotationSymbolProvider(),
|
||||
DefaultBuiltIns.Instance,
|
||||
Fir2IrCommonMemberStorage(signatureComposerForJvmFir2Ir(false), FirJvmKotlinMangler()),
|
||||
initializedIrBuiltIns = null
|
||||
)
|
||||
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
val bindingContext = NoScopeRecordCliBindingTrace().bindingContext
|
||||
val codegenFactory = createJvmIrCodegenFactory(effectiveConfiguration)
|
||||
|
||||
val generationState = GenerationState.Builder(
|
||||
project,
|
||||
classBuilderFactory,
|
||||
fir2IrResult.irModuleFragment.descriptor,
|
||||
bindingContext,
|
||||
filesToCompile,
|
||||
effectiveConfiguration,
|
||||
).generateDeclaredClassFilter(generateClassFilter)
|
||||
.codegenFactory(codegenFactory)
|
||||
.build()
|
||||
|
||||
try {
|
||||
generationState.beforeCompile()
|
||||
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
codegenFactory.generateModuleInFrontendIRMode(
|
||||
generationState,
|
||||
fir2IrResult.irModuleFragment,
|
||||
fir2IrResult.components.symbolTable,
|
||||
fir2IrResult.components.irProviders,
|
||||
JvmFir2IrExtensions(effectiveConfiguration, JvmIrDeserializerImpl(), JvmIrMangler),
|
||||
FirJvmBackendExtension(fir2IrResult.components, null),
|
||||
fir2IrResult.pluginContext
|
||||
)
|
||||
|
||||
CodegenFactory.doCheckCancelled(generationState)
|
||||
generationState.factory.done()
|
||||
|
||||
val backendDiagnostics = generationState.collectedExtraJvmDiagnostics.all()
|
||||
val backendErrors = computeErrors(backendDiagnostics, effectiveConfiguration)
|
||||
|
||||
if (backendErrors.isNotEmpty()) {
|
||||
return KtCompilationResult.Failure(backendErrors)
|
||||
}
|
||||
|
||||
val outputFiles = generationState.factory.asList().map(::KtCompiledFileForOutputFile)
|
||||
return KtCompilationResult.Success(outputFiles)
|
||||
} finally {
|
||||
generationState.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFullyResolvedFirFile(file: KtFile): FirFile {
|
||||
val firFile = file.getOrBuildFirFile(firResolveSession)
|
||||
LLFirWholeFileResolveTarget(firFile).resolve(FirResolvePhase.BODY_RESOLVE)
|
||||
return firFile
|
||||
}
|
||||
|
||||
private fun computeErrors(diagnostics: Collection<DiagnosticMarker>, configuration: CompilerConfiguration): List<KtDiagnostic> {
|
||||
val allowedErrorFilter = configuration[ALLOWED_ERROR_FILTER] ?: { true }
|
||||
|
||||
return buildList {
|
||||
for (diagnostic in diagnostics) {
|
||||
require(diagnostic is KtPsiDiagnostic)
|
||||
|
||||
if (diagnostic.severity == Severity.ERROR) {
|
||||
val ktDiagnostic = diagnostic.asKtDiagnostic()
|
||||
if (!allowedErrorFilter(ktDiagnostic)) {
|
||||
add(ktDiagnostic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SingleFileGenerateClassFilter(
|
||||
private val file: KtFile,
|
||||
private val inlinedClasses: Set<KtClassOrObject>
|
||||
) : GenerationState.GenerateClassFilter() {
|
||||
private val filesWithInlinedClasses = inlinedClasses.mapTo(mutableSetOf()) { it.containingKtFile }
|
||||
|
||||
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean {
|
||||
return file === ktFile || ktFile in filesWithInlinedClasses
|
||||
}
|
||||
|
||||
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean {
|
||||
return processingClassOrObject.containingKtFile === file ||
|
||||
processingClassOrObject is KtObjectDeclaration && processingClassOrObject in inlinedClasses
|
||||
}
|
||||
|
||||
override fun shouldGenerateScript(script: KtScript): Boolean {
|
||||
return script.containingKtFile === file
|
||||
}
|
||||
|
||||
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||
}
|
||||
|
||||
private fun createJvmIrCodegenFactory(configuration: CompilerConfiguration): JvmIrCodegenFactory {
|
||||
val jvmGeneratorExtensions = 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)
|
||||
}
|
||||
}
|
||||
|
||||
val ideCodegenSettings = JvmIrCodegenFactory.IdeCodegenSettings(
|
||||
shouldStubAndNotLinkUnboundSymbols = true,
|
||||
shouldDeduplicateBuiltInSymbols = false,
|
||||
|
||||
// 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 = false, // TODO it was true
|
||||
)
|
||||
|
||||
return JvmIrCodegenFactory(
|
||||
configuration,
|
||||
PhaseConfig(jvmPhases),
|
||||
jvmGeneratorExtensions = jvmGeneratorExtensions,
|
||||
ideCodegenSettings = ideCodegenSettings,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ dependencies {
|
||||
api(project(":analysis:analysis-api-impl-barebone"))
|
||||
api(project(":analysis:kt-references"))
|
||||
api(project(":compiler:resolution.common.jvm"))
|
||||
implementation(project(":compiler:backend-common"))
|
||||
api(intellijCore())
|
||||
implementation(project(":analysis:analysis-internal-utils"))
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.impl.base.util
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.components.KtCompiledFile
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile
|
||||
import java.io.File
|
||||
|
||||
class KtCompiledFileForOutputFile(private val outputFile: OutputFile) : KtCompiledFile {
|
||||
override val path: String
|
||||
get() = outputFile.relativePath
|
||||
|
||||
override val sourceFiles: List<File>
|
||||
get() = outputFile.sourceFiles
|
||||
|
||||
override val content: ByteArray
|
||||
get() = outputFile.asByteArray()
|
||||
}
|
||||
@@ -12,6 +12,7 @@ dependencies {
|
||||
compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
|
||||
|
||||
compileOnly(project(":compiler:psi"))
|
||||
implementation(project(":compiler:backend"))
|
||||
compileOnly(project(":core:compiler.common"))
|
||||
compileOnly(project(":core:compiler.common.jvm"))
|
||||
compileOnly(project(":core:compiler.common.js"))
|
||||
|
||||
@@ -64,7 +64,8 @@ public abstract class KtAnalysisSession(final override val token: KtLifetimeToke
|
||||
KtSignatureSubstitutorMixIn,
|
||||
KtScopeSubstitutionMixIn,
|
||||
KtSymbolProviderByJavaPsiMixIn,
|
||||
KtResolveExtensionInfoProviderMixIn {
|
||||
KtResolveExtensionInfoProviderMixIn,
|
||||
KtCompilerFacilityMixIn {
|
||||
|
||||
public abstract val useSiteModule: KtModule
|
||||
|
||||
@@ -165,6 +166,9 @@ public abstract class KtAnalysisSession(final override val token: KtLifetimeToke
|
||||
internal val resolveExtensionInfoProvider: KtResolveExtensionInfoProvider get() = resolveExtensionInfoProviderImpl
|
||||
protected abstract val resolveExtensionInfoProviderImpl: KtResolveExtensionInfoProvider
|
||||
|
||||
internal val compilerFacility: KtCompilerFacility get() = compilerFacilityImpl
|
||||
protected abstract val compilerFacilityImpl: KtCompilerFacility
|
||||
|
||||
@KtAnalysisApiInternals
|
||||
public val substitutorFactory: KtSubstitutorFactory get() = substitutorFactoryImpl
|
||||
protected abstract val substitutorFactoryImpl: KtSubstitutorFactory
|
||||
|
||||
+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.api.components
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.diagnostics.KtDiagnostic
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Compilation result.
|
||||
*/
|
||||
public sealed class KtCompilationResult {
|
||||
/**
|
||||
* Successful compilation result.
|
||||
*
|
||||
* @property output Output files produced by the compiler. For the JVM target, these are class files and '.kotlin_module'.
|
||||
*/
|
||||
public class Success(public val output: List<KtCompiledFile>) : KtCompilationResult()
|
||||
|
||||
/**
|
||||
* Failed compilation result.
|
||||
*
|
||||
* @property errors Non-recoverable errors either during code analysis, or during code generation.
|
||||
*/
|
||||
public class Failure(public val errors: List<KtDiagnostic>) : KtCompilationResult()
|
||||
}
|
||||
|
||||
public interface KtCompiledFile {
|
||||
/**
|
||||
* Path of the compiled file relative to the root of the output directory.
|
||||
*/
|
||||
public val path: String
|
||||
|
||||
/**
|
||||
* Source files that were compiled to produce this file.
|
||||
*/
|
||||
public val sourceFiles: List<File>
|
||||
|
||||
/**
|
||||
* Content of the compiled file.
|
||||
*/
|
||||
public val content: ByteArray
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if the compiled file is a Java class file.
|
||||
*/
|
||||
public val KtCompiledFile.isClassFile: Boolean
|
||||
get() = path.endsWith(".class", ignoreCase = true)
|
||||
|
||||
/**
|
||||
* Compilation target platform.
|
||||
*/
|
||||
public sealed class KtCompilerTarget {
|
||||
/** JVM target (produces '.class' files). */
|
||||
public class Jvm(public val classBuilderFactory: ClassBuilderFactory) : KtCompilerTarget()
|
||||
}
|
||||
|
||||
public abstract class KtCompilerFacility : KtAnalysisSessionComponent() {
|
||||
public companion object {
|
||||
/** Filter for the allowed errors. Compilation will be aborted if there are errors that this filter rejects. */
|
||||
public val ALLOWED_ERROR_FILTER: CompilerConfigurationKey<(KtDiagnostic) -> Boolean> =
|
||||
CompilerConfigurationKey<(KtDiagnostic) -> Boolean>("error filter")
|
||||
}
|
||||
|
||||
public abstract fun compile(file: KtFile, configuration: CompilerConfiguration, target: KtCompilerTarget): KtCompilationResult
|
||||
}
|
||||
|
||||
public interface KtCompilerFacilityMixIn : KtAnalysisSessionMixIn {
|
||||
/**
|
||||
* Compile the given [file].
|
||||
*
|
||||
* @param file A file to compile.
|
||||
* The file must be either a source module file, or a [KtCodeFragment].
|
||||
* For a [KtCodeFragment], a source module context, a compiled library source context, or an empty context(`null`) are supported.
|
||||
*
|
||||
* @param configuration Compiler configuration.
|
||||
* It is recommended to submit at least the module name ([CommonConfigurationKeys.MODULE_NAME])
|
||||
* and language version settings ([CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS]).
|
||||
*
|
||||
* @param target Compilation target platform.
|
||||
*
|
||||
* @return Compilation result.
|
||||
*
|
||||
* The function _does not_ wrap unchecked exceptions from the compiler.
|
||||
* The implementation should wrap the `compile()` call into a `try`/`catch` block if necessary.
|
||||
*/
|
||||
public fun compile(file: KtFile, configuration: CompilerConfiguration, target: KtCompilerTarget): KtCompilationResult {
|
||||
return withValidityAssertion {
|
||||
analysisSession.compilerFacility.compile(file, configuration, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -5,9 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets
|
||||
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirResolvableSession
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirSession
|
||||
import org.jetbrains.kotlin.fir.FirElementWithResolveState
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
||||
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
|
||||
|
||||
|
||||
/**
|
||||
@@ -47,3 +51,17 @@ sealed class LLFirResolveTarget {
|
||||
|
||||
protected abstract fun toStringForTarget(): String
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the target to the specified [phase].
|
||||
* The owning session must be a resolvable one.
|
||||
*/
|
||||
fun LLFirResolveTarget.resolve(phase: FirResolvePhase) {
|
||||
val session = firFile.llFirResolvableSession
|
||||
?: errorWithAttachment("Resolvable session expected, got '${firFile.llFirSession::class.java}'") {
|
||||
withEntry("firSession", firFile.llFirSession) { it.toString() }
|
||||
}
|
||||
|
||||
val lazyDeclarationResolver = session.moduleComponents.firModuleLazyDeclarationResolver
|
||||
lazyDeclarationResolver.lazyResolveTarget(this, phase, towerDataContextCollector = null)
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.compile
|
||||
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.contracts.FirContractDescription
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.hasBody
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isInline
|
||||
import org.jetbrains.kotlin.fir.expressions.FirBlock
|
||||
import org.jetbrains.kotlin.fir.expressions.FirResolvable
|
||||
import org.jetbrains.kotlin.fir.expressions.impl.FirContractCallBlock
|
||||
import org.jetbrains.kotlin.fir.psi
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
|
||||
import org.jetbrains.kotlin.fir.unwrapSubstitutionOverrides
|
||||
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
/**
|
||||
* Processes the declaration, collecting files that would need to be submitted to the backend (or handled specially)
|
||||
* in case if the declaration is compiled.
|
||||
*
|
||||
* Besides the file that owns the declaration, the visitor also recursively collects source files with called inline functions.
|
||||
* In addition, the visitor collects a list of inlined local classes. Such a list might be useful in [GenerationState.GenerateClassFilter]
|
||||
* to filter out class files unrelated to the current compilation.
|
||||
*
|
||||
* Note that compiled declarations are not analyzed, as the backend can inline them natively.
|
||||
*/
|
||||
object CompilationPeerCollector {
|
||||
fun process(declaration: FirDeclaration): CompilationPeerData {
|
||||
val visitor = CompilationPeerCollectingVisitor()
|
||||
visitor.process(declaration)
|
||||
return CompilationPeerData(visitor.files, visitor.inlinedClasses)
|
||||
}
|
||||
}
|
||||
|
||||
class CompilationPeerData(
|
||||
/** File with the original declaration and all files with called inline functions. */
|
||||
val files: List<KtFile>,
|
||||
|
||||
/** Local classes inlined as a part of inline functions. */
|
||||
val inlinedClasses: Set<KtClassOrObject>
|
||||
)
|
||||
|
||||
private class CompilationPeerCollectingVisitor : FirDefaultVisitorVoid() {
|
||||
private val processed = mutableSetOf<FirDeclaration>()
|
||||
private val queue = ArrayDeque<FirDeclaration>()
|
||||
|
||||
private val collectedFiles = mutableSetOf<KtFile>()
|
||||
private val collectedInlinedClasses = mutableSetOf<KtClassOrObject>()
|
||||
private var isInlineFunctionContext = false
|
||||
|
||||
val files: List<KtFile>
|
||||
get() = collectedFiles.toList()
|
||||
|
||||
val inlinedClasses: Set<KtClassOrObject>
|
||||
get() = collectedInlinedClasses
|
||||
|
||||
fun process(declaration: FirDeclaration) {
|
||||
processSingle(declaration)
|
||||
|
||||
while (queue.isNotEmpty()) {
|
||||
processSingle(queue.removeFirst())
|
||||
}
|
||||
}
|
||||
|
||||
private fun processSingle(declaration: FirDeclaration) {
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
if (processed.add(declaration)) {
|
||||
val containingFile = declaration.psi?.containingFile
|
||||
if (containingFile is KtFile && !containingFile.isCompiled) {
|
||||
collectedFiles.add(containingFile)
|
||||
declaration.accept(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitElement(element: FirElement) {
|
||||
if (element is FirResolvable) {
|
||||
processResolvable(element)
|
||||
}
|
||||
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitBlock(block: FirBlock) {
|
||||
if (block !is FirContractCallBlock) {
|
||||
super.visitBlock(block)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitContractDescription(contractDescription: FirContractDescription) {
|
||||
// Skip contract description.
|
||||
// Contract blocks are skipped in BE, so we would never need to inline contract DSL calls.
|
||||
}
|
||||
|
||||
override fun visitConstructor(constructor: FirConstructor) {
|
||||
constructor.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
|
||||
super.visitConstructor(constructor)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction) {
|
||||
simpleFunction.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
|
||||
val oldIsInlineFunctionContext = isInlineFunctionContext
|
||||
try {
|
||||
isInlineFunctionContext = simpleFunction.isInline
|
||||
super.visitFunction(simpleFunction)
|
||||
} finally {
|
||||
isInlineFunctionContext = oldIsInlineFunctionContext
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitProperty(property: FirProperty) {
|
||||
property.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
|
||||
|
||||
super.visitProperty(property)
|
||||
}
|
||||
|
||||
override fun visitClass(klass: FirClass) {
|
||||
super.visitClass(klass)
|
||||
|
||||
if (isInlineFunctionContext) {
|
||||
collectedInlinedClasses.addIfNotNull(klass.psi as? KtClassOrObject)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(SymbolInternals::class)
|
||||
private fun processResolvable(element: FirResolvable) {
|
||||
fun addToQueue(function: FirFunction?) {
|
||||
val original = function?.unwrapSubstitutionOverrides() ?: return
|
||||
if (original.isInline && original.hasBody) {
|
||||
queue.add(function)
|
||||
}
|
||||
}
|
||||
|
||||
val reference = element.calleeReference
|
||||
if (reference !is FirResolvedNamedReference) {
|
||||
return
|
||||
}
|
||||
|
||||
val symbol = reference.resolvedSymbol
|
||||
if (symbol is FirCallableSymbol<*>) {
|
||||
when (val fir = symbol.fir) {
|
||||
is FirFunction -> {
|
||||
addToQueue(fir)
|
||||
}
|
||||
is FirProperty -> {
|
||||
addToQueue(fir.getter)
|
||||
addToQueue(fir.setter)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// FILE: lib.kt
|
||||
package lib
|
||||
|
||||
inline fun lib() {}
|
||||
|
||||
// FILE: unrelated.kt
|
||||
package lib
|
||||
|
||||
fun unrelated() {}
|
||||
|
||||
// FILE: main.kt
|
||||
package test
|
||||
|
||||
import lib.*
|
||||
|
||||
fun foo() {
|
||||
lib()
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
File lib.kt
|
||||
File main.kt
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// FILE: base.kt
|
||||
package base
|
||||
|
||||
inline fun base() {}
|
||||
|
||||
// FILE: lib.kt
|
||||
package lib
|
||||
|
||||
import base.*
|
||||
|
||||
inline fun lib() {
|
||||
base()
|
||||
}
|
||||
|
||||
// FILE: main.kt
|
||||
package test
|
||||
|
||||
import lib.*
|
||||
|
||||
fun foo() {
|
||||
lib()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
File base.kt
|
||||
File lib.kt
|
||||
File main.kt
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// WITH_STDLIB
|
||||
|
||||
package test
|
||||
|
||||
import lib.*
|
||||
|
||||
fun foo() {
|
||||
println("foo")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
File libraryFunctionCall.kt
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// FILE: lib.kt
|
||||
package lib
|
||||
|
||||
fun lib() {}
|
||||
|
||||
// FILE: main.kt
|
||||
package test
|
||||
|
||||
import lib.*
|
||||
|
||||
fun foo() {
|
||||
lib()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
File main.kt
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// FILE: lib.kt
|
||||
package lib
|
||||
|
||||
inline val lib: String
|
||||
get() = "lib"
|
||||
|
||||
// FILE: main.kt
|
||||
package test
|
||||
|
||||
import lib.*
|
||||
|
||||
fun foo() {
|
||||
lib.length
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
File lib.kt
|
||||
File main.kt
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirFile
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.CompilationPeerCollector
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator
|
||||
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
|
||||
import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiBasedSingleModuleTest
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.model.TestModule
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.assertions
|
||||
|
||||
abstract class AbstractCompilationPeerAnalysisTest : AbstractAnalysisApiBasedSingleModuleTest() {
|
||||
override val configurator = AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false)
|
||||
|
||||
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
|
||||
val mainKtFile = ktFiles.singleOrNull() ?: ktFiles.single { it.name == "main.kt" }
|
||||
|
||||
val project = mainKtFile.project
|
||||
val sourceModule = ProjectStructureProvider.getModule(project, mainKtFile, contextualModule = null)
|
||||
|
||||
val resolveSession = sourceModule.getFirResolveSession(project)
|
||||
val firFile = mainKtFile.getOrBuildFirFile(resolveSession)
|
||||
|
||||
val compilationPeerData = CompilationPeerCollector.process(firFile)
|
||||
|
||||
val actualItems = compilationPeerData.files.map { "File " + it.name }.sorted() +
|
||||
compilationPeerData.inlinedClasses.map { "Class " + it.name }
|
||||
|
||||
val actualText = actualItems.joinToString(separator = "\n")
|
||||
|
||||
testServices.assertions.assertEqualsToTestDataFileSibling(actual = actualText)
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.analysis.api.GenerateAnalysisApiTestsKt}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("analysis/low-level-api-fir/testdata/compilationPeers")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
public class CompilationPeerAnalysisTestGenerated extends AbstractCompilationPeerAnalysisTest {
|
||||
@Test
|
||||
public void testAllFilesPresentInCompilationPeers() throws Exception {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/low-level-api-fir/testdata/compilationPeers"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("functionCall.kt")
|
||||
public void testFunctionCall() throws Exception {
|
||||
runTest("analysis/low-level-api-fir/testdata/compilationPeers/functionCall.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("functionCallDeep.kt")
|
||||
public void testFunctionCallDeep() throws Exception {
|
||||
runTest("analysis/low-level-api-fir/testdata/compilationPeers/functionCallDeep.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("libraryFunctionCall.kt")
|
||||
public void testLibraryFunctionCall() throws Exception {
|
||||
runTest("analysis/low-level-api-fir/testdata/compilationPeers/libraryFunctionCall.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("noInline.kt")
|
||||
public void testNoInline() throws Exception {
|
||||
runTest("analysis/low-level-api-fir/testdata/compilationPeers/noInline.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("propertyCall.kt")
|
||||
public void testPropertyCall() throws Exception {
|
||||
runTest("analysis/low-level-api-fir/testdata/compilationPeers/propertyCall.kt");
|
||||
}
|
||||
}
|
||||
+4
@@ -130,6 +130,10 @@ internal fun TestGroupSuite.generateFirLowLevelApiTests() {
|
||||
testClass<AbstractClassIdTest> {
|
||||
model("classId")
|
||||
}
|
||||
|
||||
testClass<AbstractCompilationPeerAnalysisTest> {
|
||||
model("compilationPeers")
|
||||
}
|
||||
}
|
||||
|
||||
testGroup(
|
||||
|
||||
Reference in New Issue
Block a user