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 <Aleksander.Kuznetsov@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
f11c96cba2
commit
58350b83a8
+2
-1
@@ -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)
|
||||
|
||||
+84
@@ -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<KtFile>.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<KtFile>.collectReachableInlineDelegatedPropertyAccessors(): List<KtFile> {
|
||||
if (isEmpty()) return this
|
||||
|
||||
val allFiles = mutableSetOf<KtFile>()
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-40
@@ -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<KtDeclarationWithBody>()
|
||||
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.
|
||||
*/
|
||||
|
||||
+51
@@ -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 }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user