[LL API] Support code fragment compilation

This commit is contained in:
Yan Zhulanow
2023-06-20 01:46:50 +09:00
committed by Space Team
parent 9446118d7f
commit e1db3c88cf
126 changed files with 2439 additions and 14 deletions
@@ -24,6 +24,7 @@ dependencies {
implementation(project(":compiler:cli-base"))
implementation(project(":compiler:backend"))
implementation(project(":compiler:backend.jvm.entrypoint"))
implementation(project(":compiler:backend.jvm.lower"))
implementation(project(":compiler:ir.backend.common"))
implementation(project(":compiler:ir.serialization.jvm"))
api(intellijCore())
@@ -6,18 +6,22 @@
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.compile.CodeFragmentCapturedValue
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.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.LLFirResolveSession
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.CodeFragmentCapturedValueAnalyzer
import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.CompilationPeerCollector
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.jvm.*
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
@@ -33,19 +37,35 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticMarker
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
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.*
import org.jetbrains.kotlin.fir.backend.jvm.*
import org.jetbrains.kotlin.fir.declarations.FirCodeFragment
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.pipeline.applyIrGenerationExtensions
import org.jetbrains.kotlin.fir.pipeline.signatureComposerForJvmFir2Ir
import org.jetbrains.kotlin.fir.references.FirReference
import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.references.toResolvedSymbol
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.PsiIrFileEntry
import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmIrMangler
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.util.StubGeneratorExtensions
import org.jetbrains.kotlin.load.kotlin.toSourceElement
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi2ir.generators.fragments.EvaluatorFragmentInfo
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource
import org.jetbrains.kotlin.utils.addToStdlib.runIf
internal class KtFirCompilerFacility(
override val analysisSession: KtFirAnalysisSession
@@ -75,14 +95,31 @@ internal class KtFirCompilerFacility(
return KtCompilationResult.Failure(frontendErrors)
}
val codeFragmentMappings = runIf(file is KtCodeFragment) {
computeCodeFragmentMappings(file, mainFirFile, firResolveSession, configuration)
}
val compilationPeerData = CompilationPeerCollector.process(mainFirFile)
val filesToCompile = compilationPeerData.files
val filesToCompile = buildList {
val dependencyFiles = buildSet {
addAll(compilationPeerData.files)
addAll(codeFragmentMappings?.capturedFiles.orEmpty())
// The main file needs to be the last so caches for the context declarations are populated in FIR-to-IR.
remove(file)
}
addAll(dependencyFiles)
add(file)
}
val firFilesToCompile = filesToCompile.map(::getFullyResolvedFirFile)
val generateClassFilter = SingleFileGenerateClassFilter(file, compilationPeerData.inlinedClasses)
val fir2IrExtensions = JvmFir2IrExtensions(effectiveConfiguration, JvmIrDeserializerImpl(), JvmIrMangler)
val jvmGeneratorExtensions = JvmFir2IrExtensions(effectiveConfiguration, JvmIrDeserializerImpl(), JvmIrMangler)
val fir2IrExtensions = CompilerFacilityFir2IrExtensions(jvmGeneratorExtensions, codeFragmentMappings?.injectedValueProvider)
val diagnosticsReporter = DiagnosticReporterFactory.createPendingReporter()
val fir2IrConfiguration = Fir2IrConfiguration(
@@ -111,8 +148,11 @@ internal class KtFirCompilerFacility(
ProgressManager.checkCanceled()
val irGeneratorExtensions = IrGenerationExtension.getInstances(project)
fir2IrResult.components.applyIrGenerationExtensions(fir2IrResult.irModuleFragment, irGeneratorExtensions)
val bindingContext = NoScopeRecordCliBindingTrace().bindingContext
val codegenFactory = createJvmIrCodegenFactory(effectiveConfiguration)
val codegenFactory = createJvmIrCodegenFactory(effectiveConfiguration, file is KtCodeFragment, fir2IrResult.irModuleFragment)
val generationState = GenerationState.Builder(
project,
@@ -136,7 +176,7 @@ internal class KtFirCompilerFacility(
fir2IrResult.irModuleFragment,
fir2IrResult.components.symbolTable,
fir2IrResult.components.irProviders,
JvmFir2IrExtensions(effectiveConfiguration, JvmIrDeserializerImpl(), JvmIrMangler),
CompilerFacilityJvmGeneratorExtensions(jvmGeneratorExtensions),
FirJvmBackendExtension(fir2IrResult.components, null),
fir2IrResult.pluginContext
)
@@ -152,7 +192,8 @@ internal class KtFirCompilerFacility(
}
val outputFiles = generationState.factory.asList().map(::KtCompiledFileForOutputFile)
return KtCompilationResult.Success(outputFiles)
val capturedValues = codeFragmentMappings?.capturedValues ?: emptyList()
return KtCompilationResult.Success(outputFiles, capturedValues)
} finally {
generationState.destroy()
}
@@ -182,6 +223,74 @@ internal class KtFirCompilerFacility(
}
}
private class CodeFragmentMappings(
val capturedValues: List<CodeFragmentCapturedValue>,
val capturedFiles: List<KtFile>,
val injectedValueProvider: InjectedSymbolProvider
)
private fun computeCodeFragmentMappings(
mainKtFile: KtFile,
mainFirFile: FirFile,
resolveSession: LLFirResolveSession,
configuration: CompilerConfiguration,
): CodeFragmentMappings {
val codeFragment = mainFirFile.declarations.single() as FirCodeFragment
val capturedData = CodeFragmentCapturedValueAnalyzer.analyze(resolveSession, codeFragment)
val capturedSymbols = capturedData.symbols
val capturedValues = capturedSymbols.map { it.value }
val injectedSymbols = capturedSymbols.map { InjectedValue(it.symbol, it.typeRef, it.value.isMutated) }
codeFragment.conversionData = CodeFragmentConversionData(
classId = ClassId(FqName.ROOT, Name.identifier(configuration[CODE_FRAGMENT_CLASS_NAME] ?: "CodeFragment")),
methodName = Name.identifier(configuration[CODE_FRAGMENT_METHOD_NAME] ?: "run"),
injectedSymbols
)
val injectedSymbolMapping = injectedSymbols.associateBy { it.symbol }
val injectedValueProvider = InjectedSymbolProvider(mainKtFile, injectedSymbolMapping)
return CodeFragmentMappings(capturedValues, capturedData.files, injectedValueProvider)
}
private class InjectedSymbolProvider(
private val mainKtFile: KtFile,
private val injectedSymbolMapping: Map<FirBasedSymbol<*>, InjectedValue>
) : (FirReference, Fir2IrConversionScope) -> InjectedValue? {
override fun invoke(calleeReference: FirReference, conversionScope: Fir2IrConversionScope): InjectedValue? {
val irFile = conversionScope.containingFileIfAny()
val psiFile = (irFile?.fileEntry as? PsiIrFileEntry)?.psiFile
if (psiFile != mainKtFile) {
return null
}
val symbol = when (calleeReference) {
is FirThisReference -> calleeReference.boundSymbol
else -> calleeReference.toResolvedSymbol<FirBasedSymbol<*>>()
}
return injectedSymbolMapping[symbol]
}
}
private class CompilerFacilityJvmGeneratorExtensions(
private val delegate: JvmGeneratorExtensions
) : StubGeneratorExtensions(), JvmGeneratorExtensions by delegate {
override val rawTypeAnnotationConstructor: IrConstructor?
get() = delegate.rawTypeAnnotationConstructor
}
private class CompilerFacilityFir2IrExtensions(
delegate: Fir2IrExtensions,
private val injectedValueProvider: InjectedSymbolProvider?
) : Fir2IrExtensions by delegate {
override fun findInjectedValue(calleeReference: FirReference, conversionScope: Fir2IrConversionScope): InjectedValue? {
return injectedValueProvider?.invoke(calleeReference, conversionScope)
}
}
private class SingleFileGenerateClassFilter(
private val file: KtFile,
private val inlinedClasses: Set<KtClassOrObject>
@@ -208,7 +317,11 @@ internal class KtFirCompilerFacility(
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
}
private fun createJvmIrCodegenFactory(configuration: CompilerConfiguration): JvmIrCodegenFactory {
private fun createJvmIrCodegenFactory(
configuration: CompilerConfiguration,
isCodeFragment: Boolean,
irModuleFragment: IrModuleFragment,
): 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
@@ -235,10 +348,21 @@ internal class KtFirCompilerFacility(
shouldReferenceUndiscoveredExpectSymbols = false, // TODO it was true
)
val phaseConfig = PhaseConfig(if (isCodeFragment) jvmFragmentLoweringPhases else jvmLoweringPhases)
@OptIn(ObsoleteDescriptorBasedAPI::class)
val evaluatorFragmentInfoForPsi2Ir = runIf<EvaluatorFragmentInfo?>(isCodeFragment) {
val irFile = irModuleFragment.files.single { (it.fileEntry as? PsiIrFileEntry)?.psiFile is KtCodeFragment }
val irClass = irFile.declarations.single { it is IrClass && it.metadata is FirMetadataSource.CodeFragment } as IrClass
val irFunction = irClass.declarations.single { it is IrFunction && it !is IrConstructor } as IrFunction
EvaluatorFragmentInfo(irClass.descriptor, irFunction.descriptor, emptyList())
}
return JvmIrCodegenFactory(
configuration,
PhaseConfig(jvmPhases),
phaseConfig,
jvmGeneratorExtensions = jvmGeneratorExtensions,
evaluatorFragmentInfoForPsi2Ir = evaluatorFragmentInfoForPsi2Ir,
ideCodegenSettings = ideCodegenSettings,
)
}
@@ -43,4 +43,4 @@ fun FirBasedSymbol<*>.findPsi(): PsiElement? =
*/
fun FirDeclaration.findReferencePsi(): PsiElement? {
return psi ?: FirSyntheticFunctionInterfaceSourceProvider.findPsi(this)
}
}
@@ -0,0 +1,218 @@
/*
* 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.test.cases.generated.cases.components.compilerFacility;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analysis.api.fir.test.configurators.AnalysisApiFirTestConfiguratorFactory;
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfiguratorFactoryData;
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator;
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind;
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.FrontendKind;
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisSessionMode;
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiMode;
import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.compilerFacility.AbstractCompilerFacilityTest;
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/analysis-api/testData/components/compilerFacility/compilation")
@TestDataPath("$PROJECT_ROOT")
public class FirIdeNormalAnalysisSourceModuleCompilerFacilityTestGenerated extends AbstractCompilerFacilityTest {
@NotNull
@Override
public AnalysisApiTestConfigurator getConfigurator() {
return AnalysisApiFirTestConfiguratorFactory.INSTANCE.createConfigurator(
new AnalysisApiTestConfiguratorFactoryData(
FrontendKind.Fir,
TestModuleKind.Source,
AnalysisSessionMode.Normal,
AnalysisApiMode.Ide
)
);
}
@Test
public void testAllFilesPresentInCompilation() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/compilerFacility/compilation"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("classKinds.kt")
public void testClassKinds() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/classKinds.kt");
}
@Test
@TestMetadata("imports.kt")
public void testImports() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/imports.kt");
}
@Test
@TestMetadata("multipleFiles.kt")
public void testMultipleFiles() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/multipleFiles.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/simple.kt");
}
@Nested
@TestMetadata("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments")
@TestDataPath("$PROJECT_ROOT")
public class CodeFragments {
@Test
public void testAllFilesPresentInCodeFragments() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("customName.kt")
public void testCustomName() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/customName.kt");
}
@Test
@TestMetadata("imports.kt")
public void testImports() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/imports.kt");
}
@Test
@TestMetadata("inlineFunctionUsage.kt")
public void testInlineFunctionUsage() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/inlineFunctionUsage.kt");
}
@Test
@TestMetadata("inlineFunctionUsageSource.kt")
public void testInlineFunctionUsageSource() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/inlineFunctionUsageSource.kt");
}
@Test
@TestMetadata("local.kt")
public void testLocal() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/local.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/simple.kt");
}
@Nested
@TestMetadata("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing")
@TestDataPath("$PROJECT_ROOT")
public class Capturing {
@Test
public void testAllFilesPresentInCapturing() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("contextReceiver.kt")
public void testContextReceiver() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/contextReceiver.kt");
}
@Test
@TestMetadata("contextReceiverExplicit.kt")
public void testContextReceiverExplicit() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/contextReceiverExplicit.kt");
}
@Test
@TestMetadata("extensionReceiver.kt")
public void testExtensionReceiver() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/extensionReceiver.kt");
}
@Test
@TestMetadata("extensionReceiverExplicit.kt")
public void testExtensionReceiverExplicit() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/extensionReceiverExplicit.kt");
}
@Test
@TestMetadata("extensionReceiverLabeled.kt")
public void testExtensionReceiverLabeled() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/extensionReceiverLabeled.kt");
}
@Test
@TestMetadata("initializer.kt")
public void testInitializer() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/initializer.kt");
}
@Test
@TestMetadata("local.kt")
public void testLocal() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/local.kt");
}
@Test
@TestMetadata("localFunction.kt")
public void testLocalFunction() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localFunction.kt");
}
@Test
@TestMetadata("localMutated.kt")
public void testLocalMutated() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localMutated.kt");
}
@Test
@TestMetadata("nestedOuterClass.kt")
public void testNestedOuterClass() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/nestedOuterClass.kt");
}
@Test
@TestMetadata("objectFunction.kt")
public void testObjectFunction() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/objectFunction.kt");
}
@Test
@TestMetadata("outerClass.kt")
public void testOuterClass() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/outerClass.kt");
}
@Test
@TestMetadata("outerClassMutated.kt")
public void testOuterClassMutated() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/outerClassMutated.kt");
}
@Test
@TestMetadata("outerClassMutatedPrivate.kt")
public void testOuterClassMutatedPrivate() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/outerClassMutatedPrivate.kt");
}
@Test
@TestMetadata("valueParameter.kt")
public void testValueParameter() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/valueParameter.kt");
}
}
}
}