diff --git a/analysis/analysis-api-fe10/build.gradle.kts b/analysis/analysis-api-fe10/build.gradle.kts index 8e39a7656a8..94b697f046d 100644 --- a/analysis/analysis-api-fe10/build.gradle.kts +++ b/analysis/analysis-api-fe10/build.gradle.kts @@ -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")) diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/CliFe10AnalysisFacade.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/CliFe10AnalysisFacade.kt index 6d8f36fd5cb..b74b3e5456c 100644 --- a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/CliFe10AnalysisFacade.kt +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/CliFe10AnalysisFacade.kt @@ -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, mode: Fe10AnalysisFacade.AnalysisMode): BindingContext { + val element = elements.firstOrNull() ?: return BindingContext.EMPTY return getResolveSession(element).bindingContext } diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/Fe10AnalysisFacade.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/Fe10AnalysisFacade.kt index 38e1ee5fc41..b550351333b 100644 --- a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/Fe10AnalysisFacade.kt +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/Fe10AnalysisFacade.kt @@ -35,7 +35,11 @@ interface Fe10AnalysisFacade { fun getOverloadingConflictResolver(element: KtElement): OverloadingConflictResolver> fun getKotlinTypeRefiner(element: KtElement): KotlinTypeRefiner - fun analyze(element: KtElement, mode: AnalysisMode = AnalysisMode.FULL): BindingContext + fun analyze(elements: List, mode: AnalysisMode = AnalysisMode.FULL): BindingContext + + fun analyze(element: KtElement, mode: AnalysisMode = AnalysisMode.FULL): BindingContext { + return analyze(listOf(element), mode) + } fun getOrigin(file: VirtualFile): KtSymbolOrigin diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/KtFe10AnalysisSession.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/KtFe10AnalysisSession.kt index b87f81378a4..b115baa479e 100644 --- a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/KtFe10AnalysisSession.kt +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/KtFe10AnalysisSession.kt @@ -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 { diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/components/KtFe10CompilerFacility.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/components/KtFe10CompilerFacility.kt new file mode 100644 index 00000000000..42c487ccf14 --- /dev/null +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/components/KtFe10CompilerFacility.kt @@ -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 = CompilerConfigurationKey("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 { + 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, + ) + } +} \ No newline at end of file diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineFunctionAnalyzer.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineFunctionAnalyzer.kt new file mode 100644 index 00000000000..0e64837804e --- /dev/null +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineFunctionAnalyzer.kt @@ -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 = mutableSetOf() + private val inlineFunctionsWithBody: MutableSet = 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() + + 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 = 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 { + val results = mutableSetOf() + + 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 + } +} \ No newline at end of file diff --git a/analysis/analysis-api-fir/build.gradle.kts b/analysis/analysis-api-fir/build.gradle.kts index bc269aff0c3..16e718df8b0 100644 --- a/analysis/analysis-api-fir/build.gradle.kts +++ b/analysis/analysis-api-fir/build.gradle.kts @@ -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")) diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/KtFirAnalysisSession.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/KtFirAnalysisSession.kt index 008e4f66204..7c4ae8fd042 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/KtFirAnalysisSession.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/KtFirAnalysisSession.kt @@ -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) { diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirCompilerFacility.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirCompilerFacility.kt new file mode 100644 index 00000000000..1a2a4faab9b --- /dev/null +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirCompilerFacility.kt @@ -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, configuration: CompilerConfiguration): List { + 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 + ) : 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, + ) + } +} \ No newline at end of file diff --git a/analysis/analysis-api-impl-base/build.gradle.kts b/analysis/analysis-api-impl-base/build.gradle.kts index 111d977c4bb..8d844132a70 100644 --- a/analysis/analysis-api-impl-base/build.gradle.kts +++ b/analysis/analysis-api-impl-base/build.gradle.kts @@ -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")) diff --git a/analysis/analysis-api-impl-base/src/org/jetbrains/kotlin/analysis/api/impl/base/util/KtCompiledFileForOutputFile.kt b/analysis/analysis-api-impl-base/src/org/jetbrains/kotlin/analysis/api/impl/base/util/KtCompiledFileForOutputFile.kt new file mode 100644 index 00000000000..95404722389 --- /dev/null +++ b/analysis/analysis-api-impl-base/src/org/jetbrains/kotlin/analysis/api/impl/base/util/KtCompiledFileForOutputFile.kt @@ -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 + get() = outputFile.sourceFiles + + override val content: ByteArray + get() = outputFile.asByteArray() +} \ No newline at end of file diff --git a/analysis/analysis-api/build.gradle.kts b/analysis/analysis-api/build.gradle.kts index f9ba36ba9e1..18bbc28e10d 100644 --- a/analysis/analysis-api/build.gradle.kts +++ b/analysis/analysis-api/build.gradle.kts @@ -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")) diff --git a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/KtAnalysisSession.kt b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/KtAnalysisSession.kt index 1d32c54e847..390de152a79 100644 --- a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/KtAnalysisSession.kt +++ b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/KtAnalysisSession.kt @@ -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 diff --git a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KtCompilerFacility.kt b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KtCompilerFacility.kt new file mode 100644 index 00000000000..c84f5b51426 --- /dev/null +++ b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KtCompilerFacility.kt @@ -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) : KtCompilationResult() + + /** + * Failed compilation result. + * + * @property errors Non-recoverable errors either during code analysis, or during code generation. + */ + public class Failure(public val errors: List) : 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 + + /** + * 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) + } + } +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirResolveTarget.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirResolveTarget.kt index 4983a937084..6709c3cb042 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirResolveTarget.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/targets/LLFirResolveTarget.kt @@ -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) +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/compile/CompilationPeerCollector.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/compile/CompilationPeerCollector.kt new file mode 100644 index 00000000000..f15ea9c7d60 --- /dev/null +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/compile/CompilationPeerCollector.kt @@ -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, + + /** Local classes inlined as a part of inline functions. */ + val inlinedClasses: Set +) + +private class CompilationPeerCollectingVisitor : FirDefaultVisitorVoid() { + private val processed = mutableSetOf() + private val queue = ArrayDeque() + + private val collectedFiles = mutableSetOf() + private val collectedInlinedClasses = mutableSetOf() + private var isInlineFunctionContext = false + + val files: List + get() = collectedFiles.toList() + + val inlinedClasses: Set + 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 -> {} + } + } + } +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/functionCall.kt b/analysis/low-level-api-fir/testdata/compilationPeers/functionCall.kt new file mode 100644 index 00000000000..0f6afaf1b15 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/functionCall.kt @@ -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() +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/functionCall.txt b/analysis/low-level-api-fir/testdata/compilationPeers/functionCall.txt new file mode 100644 index 00000000000..f2264c4c63f --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/functionCall.txt @@ -0,0 +1,2 @@ +File lib.kt +File main.kt \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/functionCallDeep.kt b/analysis/low-level-api-fir/testdata/compilationPeers/functionCallDeep.kt new file mode 100644 index 00000000000..cb2c22fdeb5 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/functionCallDeep.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() +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/functionCallDeep.txt b/analysis/low-level-api-fir/testdata/compilationPeers/functionCallDeep.txt new file mode 100644 index 00000000000..a437a987faf --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/functionCallDeep.txt @@ -0,0 +1,3 @@ +File base.kt +File lib.kt +File main.kt \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/libraryFunctionCall.kt b/analysis/low-level-api-fir/testdata/compilationPeers/libraryFunctionCall.kt new file mode 100644 index 00000000000..5380efa2288 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/libraryFunctionCall.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") +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/libraryFunctionCall.txt b/analysis/low-level-api-fir/testdata/compilationPeers/libraryFunctionCall.txt new file mode 100644 index 00000000000..b12b535e165 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/libraryFunctionCall.txt @@ -0,0 +1 @@ +File libraryFunctionCall.kt \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/noInline.kt b/analysis/low-level-api-fir/testdata/compilationPeers/noInline.kt new file mode 100644 index 00000000000..892dfbb7f51 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/noInline.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() +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/noInline.txt b/analysis/low-level-api-fir/testdata/compilationPeers/noInline.txt new file mode 100644 index 00000000000..c78ab3e563b --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/noInline.txt @@ -0,0 +1 @@ +File main.kt \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/propertyCall.kt b/analysis/low-level-api-fir/testdata/compilationPeers/propertyCall.kt new file mode 100644 index 00000000000..ee80206a357 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/propertyCall.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 +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/compilationPeers/propertyCall.txt b/analysis/low-level-api-fir/testdata/compilationPeers/propertyCall.txt new file mode 100644 index 00000000000..f2264c4c63f --- /dev/null +++ b/analysis/low-level-api-fir/testdata/compilationPeers/propertyCall.txt @@ -0,0 +1,2 @@ +File lib.kt +File main.kt \ No newline at end of file diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractCompilationPeerAnalysisTest.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractCompilationPeerAnalysisTest.kt new file mode 100644 index 00000000000..1ed85ab0460 --- /dev/null +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractCompilationPeerAnalysisTest.kt @@ -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, 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) + } +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/CompilationPeerAnalysisTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/CompilationPeerAnalysisTestGenerated.java new file mode 100644 index 00000000000..6d672110abb --- /dev/null +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/CompilationPeerAnalysisTestGenerated.java @@ -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"); + } +} diff --git a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt index 319f949ad9e..a696b219747 100644 --- a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt +++ b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt @@ -130,6 +130,10 @@ internal fun TestGroupSuite.generateFirLowLevelApiTests() { testClass { model("classId") } + + testClass { + model("compilationPeers") + } } testGroup(