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 index b391c598b9b..406def4f78b 100644 --- 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 @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisFacade.Analysis 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.descriptors.utils.collectReachableInlineDelegatedPropertyAccessors 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 @@ -73,7 +74,7 @@ internal class KtFe10CompilerFacility( val inlineAnalyzer = InlineFunctionAnalyzer(analysisContext, analyzeOnlyReifiedInlineFunctions = disableInline) inlineAnalyzer.analyze(file) - val filesToCompile = inlineAnalyzer.allFiles() + val filesToCompile = inlineAnalyzer.allFiles().collectReachableInlineDelegatedPropertyAccessors() val bindingContext = analysisContext.analyze(filesToCompile, AnalysisMode.ALL_COMPILER_CHECKS) val frontendErrors = computeErrors(bindingContext.diagnostics, allowedErrorFilter) diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineDelegatedPropertyAccessorsAnalyzer.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineDelegatedPropertyAccessorsAnalyzer.kt new file mode 100644 index 00000000000..4e51d334041 --- /dev/null +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineDelegatedPropertyAccessorsAnalyzer.kt @@ -0,0 +1,84 @@ +/* + * 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.KtAnalysisNonPublicApi +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisContext +import org.jetbrains.kotlin.analysis.api.descriptors.KtFe10AnalysisSession +import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors +import org.jetbrains.kotlin.descriptors.accessors +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.resolve.BindingContext + +@OptIn(KtAnalysisNonPublicApi::class) +fun MutableSet.collectReachableInlineDelegatedPropertyAccessors() { + if (isEmpty()) return + + // One of the compiler lowerings, namely `PropertyReferenceLowering`, + // optimizes usages of property references in some cases, + // and if a containing delegated property accessor is inline, + // it might need this accessor's bytecode. + // + // If an accessor function is defined in a different module, + // IDE tries to acquire its bytecode via the index, however, + // the index doesn't cover classfiles from compiler output, + // so the lowering fails. + // + // To solve the problem, we need to find all delegated properties with inline accessors + // reachable from files that will be compiled, and include files these inline accessors + // to the set of files that will be compiled (and do the same for these files recursively). + // As it's basically a DAG traversal, we can keep a queue instead of making recursive calls. + val allFiles = this + val filesQueueToAnalyze = ArrayDeque(allFiles) + val collector = InlineFunctionsCollector(allFiles.first().project, reifiedInlineFunctionsOnly = false) { declaration -> + val containingFile = declaration.containingKtFile + if (allFiles.add(containingFile)) { + filesQueueToAnalyze += containingFile + } + } + while (filesQueueToAnalyze.isNotEmpty()) { + val file = filesQueueToAnalyze.removeFirst() + analyze(file) { + require(this is KtFe10AnalysisSession) { + "K2 implementation shouldn't call this code" + } + file.accept(InlineDelegatedPropertyAccessorsAnalyzer(analysisContext, collector)) + } + } +} + +@OptIn(KtAnalysisNonPublicApi::class) +fun List.collectReachableInlineDelegatedPropertyAccessors(): List { + if (isEmpty()) return this + + val allFiles = mutableSetOf() + allFiles.addAll(this) + allFiles.collectReachableInlineDelegatedPropertyAccessors() + return allFiles.toList() +} + +internal class InlineDelegatedPropertyAccessorsAnalyzer( + private val analysisContext: Fe10AnalysisContext, + private val collector: InlineFunctionsCollector +) : KtTreeVisitorVoid() { + + override fun visitProperty(property: KtProperty) { + super.visitProperty(property) + val isDelegate = property.hasDelegateExpression() + if (!isDelegate) return + + val bindingContext = analysisContext.analyze(property) + val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, property) + if (descriptor !is VariableDescriptorWithAccessors) return + + descriptor.accessors.forEach { accessor -> + collector.checkResolveCall(bindingContext.get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, accessor)) + } + } +} 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 index 0e64837804e..a16a7e4295d 100644 --- 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 @@ -5,16 +5,23 @@ package org.jetbrains.kotlin.analysis.api.descriptors.utils +import org.jetbrains.kotlin.analysis.api.KtAnalysisNonPublicApi +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisContext -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.analysis.api.descriptors.KtFe10AnalysisSession 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 +@OptIn(KtAnalysisNonPublicApi::class) // used in IDEA K1 evaluator +@Suppress("unused") +fun KtAnalysisSession.getInlineFunctionAnalyzer(analyzeOnlyReifiedInlineFunctions: Boolean): InlineFunctionAnalyzer { + require(this is KtFe10AnalysisSession) { + "K2 implementation shouldn't call this code" + } + return InlineFunctionAnalyzer(analysisContext, analyzeOnlyReifiedInlineFunctions) +} + +@OptIn(KtAnalysisNonPublicApi::class) class InlineFunctionAnalyzer( private val analysisContext: Fe10AnalysisContext, private val analyzeOnlyReifiedInlineFunctions: Boolean, @@ -28,6 +35,12 @@ class InlineFunctionAnalyzer( fun analyze(element: KtElement) { val project = element.project val nextInlineFunctions = HashSet() + val collector = InlineFunctionsCollector(project, analyzeOnlyReifiedInlineFunctions) { declaration -> + if (!analyzedElements.contains(declaration)) { + nextInlineFunctions.add(declaration) + } + } + val propertyAccessor = InlineDelegatedPropertyAccessorsAnalyzer(analysisContext, collector) element.accept(object : KtTreeVisitorVoid() { override fun visitExpression(expression: KtExpression) { @@ -36,7 +49,7 @@ class InlineFunctionAnalyzer( val bindingContext = analysisContext.analyze(expression) val call = bindingContext.get(BindingContext.CALL, expression) ?: return val resolvedCall = bindingContext.get(BindingContext.RESOLVED_CALL, call) - checkResolveCall(resolvedCall) + collector.checkResolveCall(resolvedCall) } override fun visitDestructuringDeclaration(destructuringDeclaration: KtDestructuringDeclaration) { @@ -46,7 +59,7 @@ class InlineFunctionAnalyzer( for (entry in destructuringDeclaration.entries) { val resolvedCall = bindingContext.get(BindingContext.COMPONENT_RESOLVED_CALL, entry) - checkResolveCall(resolvedCall) + collector.checkResolveCall(resolvedCall) } } @@ -55,36 +68,14 @@ class InlineFunctionAnalyzer( 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)) + collector.checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL, expression.loopRange)) + collector.checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_HAS_NEXT_RESOLVED_CALL, expression.loopRange)) + collector.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 - } + override fun visitProperty(property: KtProperty) { + super.visitProperty(property) + propertyAccessor.visitProperty(property) } }) @@ -101,10 +92,6 @@ class InlineFunctionAnalyzer( } } - private fun hasReifiedTypeParameters(descriptor: CallableDescriptor): Boolean { - return descriptor.typeParameters.any { it.isReified } - } - /** * Returns the list of files that contain all reached inline functions. */ diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineFunctionsCollector.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineFunctionsCollector.kt new file mode 100644 index 00000000000..f02479a2b60 --- /dev/null +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineFunctionsCollector.kt @@ -0,0 +1,51 @@ +/* + * 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 com.intellij.openapi.project.Project +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.psi.KtDeclarationWithBody +import org.jetbrains.kotlin.references.fe10.util.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.inline.InlineUtil +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor + +internal class InlineFunctionsCollector( + private val project: Project, + private val reifiedInlineFunctionsOnly: Boolean, + private val acceptDeclaration: (KtDeclarationWithBody) -> Unit +) { + 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) || reifiedInlineFunctionsOnly && !hasReifiedTypeParameters(descriptor)) { + return + } + + val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) + if (declaration != null && declaration is KtDeclarationWithBody) { + acceptDeclaration(declaration) + } + } + + private fun hasReifiedTypeParameters(descriptor: CallableDescriptor): Boolean { + return descriptor.typeParameters.any { it.isReified } + } +}