From 58350b83a8ac28c01f71175c2c30240bcd9bb464 Mon Sep 17 00:00:00 2001 From: Alexander Kuznetsov Date: Tue, 29 Aug 2023 23:55:31 +0000 Subject: [PATCH] KtFe10CompilerFacility: analyze inline delegated property accessors 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, and that leads to errors like KTIJ-26706 and IDEA-320283. One of the ways to ensure that the bytecode exists is to include files that contain inline accessors in the compilation process. For example, if file f1.kt from the analysis result contains property `val p by D()`, and `D.getValue` is an inline function defined in file f2.kt, then f2.kt must be present in the analysis result. If f2.kt contains another property `val q by E()`, and `E.getValue` is an inline function defined in f3.kt, then f3.kt must be present in the analysis result. And so on. Fixes KTIJ-26706, IDEA-320283 Merge-request: KT-MR-11793 Merged-by: Alexander Kuznetsov --- .../components/KtFe10CompilerFacility.kt | 3 +- ...nlineDelegatedPropertyAccessorsAnalyzer.kt | 84 +++++++++++++++++++ .../utils/InlineFunctionAnalyzer.kt | 67 ++++++--------- .../utils/InlineFunctionsCollector.kt | 51 +++++++++++ 4 files changed, 164 insertions(+), 41 deletions(-) create mode 100644 analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineDelegatedPropertyAccessorsAnalyzer.kt create mode 100644 analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/utils/InlineFunctionsCollector.kt 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 } + } +}