Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.checkers.utils.CheckerTestUtil.kt Maven / Gradle / Ivy
package org.jetbrains.kotlin.checkers.utils
import com.google.common.collect.LinkedListMultimap
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.checkers.*
import org.jetbrains.kotlin.checkers.diagnostics.*
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory0
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.platform.oldFashionedDescription
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getCall
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.noTypeInfo
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import java.util.*
import java.util.regex.Pattern
data class DiagnosticsRenderingConfiguration (
val platform: String ?,
val withNewInference: Boolean ,
val languageVersionSettings: LanguageVersionSettings ?,
val skipDebugInfoDiagnostics: Boolean = false,
)
object CheckerTestUtil {
const val NEW_INFERENCE_PREFIX = "NI"
const val OLD_INFERENCE_PREFIX = "OI"
private const val IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE"
private const val INDIVIDUAL_DIAGNOSTIC = "" "(\w+;)?(\w+:)?(\w+)(\{[\w;]+})?(?:\(((?:" .*?")(?:,\s*" .*?")*)\))?" ""
val rangeStartOrEndPattern = Pattern .compile("()|()" )
val individualDiagnosticPattern: Pattern = Pattern .compile(INDIVIDUAL_DIAGNOSTIC )
fun getDiagnosticsIncludingSyntaxErrors(
bindingContext: BindingContext ,
implementingModulesBindings: List >,
root: PsiElement ,
markDynamicCalls: Boolean ,
dynamicCallDescriptors: MutableList ,
configuration: DiagnosticsRenderingConfiguration ,
dataFlowValueFactory: DataFlowValueFactory ?,
moduleDescriptor: ModuleDescriptorImpl ?,
diagnosedRanges: MutableMap >? = null
): List {
val result = getDiagnosticsIncludingSyntaxErrors(
bindingContext,
root,
markDynamicCalls,
dynamicCallDescriptors,
configuration,
dataFlowValueFactory,
moduleDescriptor,
diagnosedRanges
)
val sortedBindings = implementingModulesBindings.sortedBy { it.first.oldFashionedDescription }
for ((platform, second) in sortedBindings) {
assert(!platform.isCommon()) { "Implementing module must have a specific platform: $platform" }
result.addAll(
getDiagnosticsIncludingSyntaxErrors(
second,
root,
markDynamicCalls,
dynamicCallDescriptors,
configuration.copy(platform = platform.single().platformName),
dataFlowValueFactory,
moduleDescriptor,
diagnosedRanges
)
)
}
return result
}
fun getDiagnosticsIncludingSyntaxErrors(
bindingContext: BindingContext ,
root: PsiElement ,
markDynamicCalls: Boolean ,
dynamicCallDescriptors: MutableList ,
configuration: DiagnosticsRenderingConfiguration ,
dataFlowValueFactory: DataFlowValueFactory ?,
moduleDescriptor: ModuleDescriptorImpl ?,
diagnosedRanges: MutableMap >? = null
): MutableList {
val diagnostics: MutableList = mutableListOf()
bindingContext.diagnostics.forEach { diagnostic ->
if (PsiTreeUtil .isAncestor(root, diagnostic.psiElement, false )) {
diagnostics.add(ActualDiagnostic (diagnostic, configuration.platform, configuration.withNewInference))
}
}
for (errorElement in AnalyzingUtils .getSyntaxErrorRanges(root)) {
diagnostics.add(ActualDiagnostic (SyntaxErrorDiagnostic (errorElement), configuration.platform, configuration.withNewInference))
}
if (!configuration.skipDebugInfoDiagnostics) {
diagnostics.addAll(
getDebugInfoDiagnostics(
root,
bindingContext,
markDynamicCalls,
dynamicCallDescriptors,
configuration,
dataFlowValueFactory,
moduleDescriptor,
diagnosedRanges
)
)
}
return diagnostics
}
fun getDebugInfoDiagnostics(
root: PsiElement ,
bindingContext: BindingContext ,
markDynamicCalls: Boolean ,
dynamicCallDescriptors: MutableList ,
configuration: DiagnosticsRenderingConfiguration ,
dataFlowValueFactory: DataFlowValueFactory ?,
moduleDescriptor: ModuleDescriptorImpl ?,
diagnosedRanges: Map >?
): List {
val debugAnnotations = mutableListOf()
DebugInfoUtil .markDebugAnnotations(
root,
bindingContext,
CheckerDebugInfoReporter (
dynamicCallDescriptors,
markDynamicCalls,
debugAnnotations,
configuration.withNewInference,
configuration.platform
)
)
val factoryListForDiagnosticsOnExpression = listOf(
BindingContext .EXPRESSION_TYPE_INFO to listOf(DebugInfoDiagnosticFactory1 .EXPRESSION_TYPE ),
BindingContext .SMARTCAST to listOf(DebugInfoDiagnosticFactory0 .SMARTCAST ),
BindingContext .IMPLICIT_RECEIVER_SMARTCAST to listOf(DebugInfoDiagnosticFactory0 .IMPLICIT_RECEIVER_SMARTCAST ),
BindingContext .SMARTCAST_NULL to listOf(DebugInfoDiagnosticFactory0 .CONSTANT ),
BindingContext .LEAKING_THIS to listOf(DebugInfoDiagnosticFactory0 .LEAKING_THIS ),
BindingContext .IMPLICIT_EXHAUSTIVE_WHEN to listOf(DebugInfoDiagnosticFactory0 .IMPLICIT_EXHAUSTIVE )
)
val factoryListForDiagnosticsOnCall = listOf(
BindingContext .RESOLVED_CALL to listOf(DebugInfoDiagnosticFactory1 .CALL , DebugInfoDiagnosticFactory1 .CALLABLE_OWNER )
)
renderDiagnosticsByFactoryList(
factoryListForDiagnosticsOnExpression, root, bindingContext, configuration,
dataFlowValueFactory, moduleDescriptor, diagnosedRanges, debugAnnotations
)
renderDiagnosticsByFactoryList(
factoryListForDiagnosticsOnCall, root, bindingContext, configuration,
dataFlowValueFactory, moduleDescriptor, diagnosedRanges, debugAnnotations
) { it.callElement }
return debugAnnotations
}
private fun renderDiagnosticsByFactoryList(
factoryList: List , List >>,
root: PsiElement ,
bindingContext: BindingContext ,
configuration: DiagnosticsRenderingConfiguration ,
dataFlowValueFactory: DataFlowValueFactory ?,
moduleDescriptor: ModuleDescriptorImpl ?,
diagnosedRanges: Map >?,
debugAnnotations: MutableList ,
elementProvider: (T ) -> KtElement ? = { it as? KtElement }
) {
for ((context, factories) in factoryList) {
for ((element, _) in bindingContext.getSliceContents(context)) {
for (factory in factories) {
renderDiagnostics(
factory,
elementProvider(element) ?: continue ,
root, bindingContext, configuration, dataFlowValueFactory, moduleDescriptor, diagnosedRanges,
debugAnnotations
)
}
}
}
}
private fun renderDiagnostics(
factory: DebugInfoDiagnosticFactory ,
element: KtElement ,
root: PsiElement ,
bindingContext: BindingContext ,
configuration: DiagnosticsRenderingConfiguration ,
dataFlowValueFactory: DataFlowValueFactory ?,
moduleDescriptor: ModuleDescriptorImpl ?,
diagnosedRanges: Map >?,
debugAnnotations: MutableList
) {
if (factory !is DiagnosticFactory <*>) return
val needRender = !factory.withExplicitDefinitionOnly
|| diagnosedRanges?.get(element.startOffset..element.endOffset)?.contains(factory.name) == true
if (PsiTreeUtil .isAncestor(root, element, false ) && needRender) {
val diagnostic = factory.createDiagnostic(
element,
bindingContext,
dataFlowValueFactory,
configuration.languageVersionSettings,
moduleDescriptor
)
debugAnnotations.add(ActualDiagnostic (diagnostic, configuration.platform, configuration.withNewInference))
}
}
fun diagnosticsDiff(
expected: List ,
actual: Collection ,
callbacks: DiagnosticDiffCallbacks
): Map {
val diagnosticToExpectedDiagnostic = mutableMapOf()
assertSameFile(actual)
val expectedDiagnostics = expected.iterator()
val sortedDiagnosticDescriptors = getActualSortedDiagnosticDescriptors(actual)
val actualDiagnostics = sortedDiagnosticDescriptors.iterator()
var currentExpected = safeAdvance(expectedDiagnostics)
var currentActual = safeAdvance(actualDiagnostics)
while (currentExpected != null || currentActual != null ) {
if (currentExpected == null ) {
assert(currentActual != null )
unexpectedDiagnostics(currentActual!!, callbacks)
currentActual = safeAdvance(actualDiagnostics)
continue
}
if (currentActual == null ) {
missingDiagnostics(callbacks, currentExpected)
currentExpected = safeAdvance(expectedDiagnostics)
continue
}
val expectedStart = currentExpected.start
val actualStart = currentActual.start
val expectedEnd = currentExpected.end
val actualEnd = currentActual.end
when {
expectedStart < actualStart -> {
missingDiagnostics(callbacks, currentExpected)
currentExpected = safeAdvance(expectedDiagnostics)
}
expectedStart > actualStart -> {
unexpectedDiagnostics(currentActual, callbacks)
currentActual = safeAdvance(actualDiagnostics)
}
expectedEnd > actualEnd -> {
assert(expectedStart == actualStart)
missingDiagnostics(callbacks, currentExpected)
currentExpected = safeAdvance(expectedDiagnostics)
}
expectedEnd < actualEnd -> {
assert(expectedStart == actualStart)
unexpectedDiagnostics(currentActual, callbacks)
currentActual = safeAdvance(actualDiagnostics)
}
else -> {
compareDiagnostics(callbacks, currentExpected, currentActual, diagnosticToExpectedDiagnostic)
currentExpected = safeAdvance(expectedDiagnostics)
currentActual = safeAdvance(actualDiagnostics)
}
}
}
return diagnosticToExpectedDiagnostic
}
private fun compareDiagnostics(
callbacks: DiagnosticDiffCallbacks ,
currentExpected: DiagnosedRange ,
currentActual: ActualDiagnosticDescriptor ,
diagnosticToInput: MutableMap
) {
val expectedStart = currentExpected.start
val expectedEnd = currentExpected.end
val actualStart = currentActual.start
val actualEnd = currentActual.end
assert(expectedStart == actualStart && expectedEnd == actualEnd)
val actualDiagnostics = currentActual.textDiagnosticsMap
val expectedDiagnostics = currentExpected.getDiagnostics()
val diagnosticNames = HashSet ()
for (expectedDiagnostic in expectedDiagnostics) {
var actualDiagnosticEntry = actualDiagnostics.entries.firstOrNull { entry ->
val actualDiagnostic = entry.value
expectedDiagnostic.description == actualDiagnostic.description
&& expectedDiagnostic.inferenceCompatibility.isCompatible(actualDiagnostic.inferenceCompatibility)
&& expectedDiagnostic.parameters == actualDiagnostic.parameters
}
if (actualDiagnosticEntry == null ) {
actualDiagnosticEntry = actualDiagnostics.entries.firstOrNull { entry ->
val actualDiagnostic = entry.value
expectedDiagnostic.description == actualDiagnostic.description
&& expectedDiagnostic.inferenceCompatibility.isCompatible(actualDiagnostic.inferenceCompatibility)
}
}
if (actualDiagnosticEntry == null ) {
callbacks.missingDiagnostic(expectedDiagnostic, expectedStart, expectedEnd)
continue
}
val actualDiagnostic = actualDiagnosticEntry.key
val actualTextDiagnostic = actualDiagnosticEntry.value
if (!compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic))
callbacks.wrongParametersDiagnostic(expectedDiagnostic, actualTextDiagnostic, expectedStart, expectedEnd)
actualDiagnostics.remove(actualDiagnostic)
diagnosticNames.add(actualDiagnostic.name)
actualDiagnostic.enhanceInferenceCompatibility(expectedDiagnostic.inferenceCompatibility)
diagnosticToInput[actualDiagnostic] = expectedDiagnostic
}
for (unexpectedDiagnostic in actualDiagnostics.keys) {
val textDiagnostic = actualDiagnostics[unexpectedDiagnostic]
if (hasExplicitDefinitionOnlyOption(unexpectedDiagnostic) && !diagnosticNames.contains(unexpectedDiagnostic.name))
continue
callbacks.unexpectedDiagnostic(textDiagnostic!!, actualStart, actualEnd)
}
}
private fun compareTextDiagnostic(expected: TextDiagnostic , actual: TextDiagnostic ): Boolean {
if (expected.description != actual.description)
return false
if (expected.parameters == null )
return true
if (actual.parameters == null || expected.parameters.size != actual.parameters.size)
return false
expected.parameters.forEachIndexed { index: Int , expectedParameter: String ->
if (expectedParameter != IGNORE_DIAGNOSTIC_PARAMETER && expectedParameter != actual.parameters[index])
return false
}
return true
}
private fun assertSameFile(actual: Collection ) {
if (actual.isEmpty()) return
val file = actual.first().file
for (actualDiagnostic in actual) {
assert(actualDiagnostic.file == file) { "All diagnostics should come from the same file: " + actualDiagnostic.file + ", " + file }
}
}
private fun unexpectedDiagnostics(descriptor: ActualDiagnosticDescriptor , callbacks: DiagnosticDiffCallbacks ) {
for (diagnostic in descriptor.diagnostics) {
if (hasExplicitDefinitionOnlyOption(diagnostic))
continue
callbacks.unexpectedDiagnostic(TextDiagnostic .asTextDiagnostic(diagnostic), descriptor.start, descriptor.end)
}
}
private fun missingDiagnostics(callbacks: DiagnosticDiffCallbacks , currentExpected: DiagnosedRange ) {
for (diagnostic in currentExpected.getDiagnostics()) {
callbacks.missingDiagnostic(diagnostic, currentExpected.start, currentExpected.end)
}
}
private fun safeAdvance(iterator: Iterator ): T ? {
return if (iterator.hasNext()) iterator.next() else null
}
fun parseDiagnosedRanges(
text: String ,
ranges: MutableList ,
rangesToDiagnosticNames: MutableMap >? = null
): String {
val matcher = rangeStartOrEndPattern.matcher(text)
val opened = Stack ()
var offsetCompensation = 0
while (matcher.find()) {
val effectiveOffset = matcher.start() - offsetCompensation
val matchedText = matcher.group()
if (matchedText == "" ) {
opened.pop().end = effectiveOffset
} else {
val diagnosticTypeMatcher = individualDiagnosticPattern.matcher(matchedText)
val range = DiagnosedRange (effectiveOffset)
while (diagnosticTypeMatcher.find())
range.addDiagnostic(diagnosticTypeMatcher.group())
opened.push(range)
ranges.add(range)
}
offsetCompensation += matchedText.length
}
assert(opened.isEmpty()) { "Stack is not empty" }
matcher.reset()
if (rangesToDiagnosticNames != null ) {
ranges.forEach {
val range = it.start..it.end
rangesToDiagnosticNames.putIfAbsent(range, mutableSetOf())
rangesToDiagnosticNames[range]!! += it.getDiagnostics().map { it.name }
}
}
return matcher.replaceAll("" )
}
private fun hasExplicitDefinitionOnlyOption(diagnostic: AbstractTestDiagnostic ): Boolean {
if (diagnostic !is ActualDiagnostic )
return false
val factory = diagnostic.diagnostic.factory
return factory is DebugInfoDiagnosticFactory && (factory as DebugInfoDiagnosticFactory ).withExplicitDefinitionOnly
}
fun addDiagnosticMarkersToText(psiFile: PsiFile , diagnostics: Collection ) =
addDiagnosticMarkersToText(
psiFile,
diagnostics,
emptyMap(),
{ it.text },
emptyList(),
false ,
false
)
fun addDiagnosticMarkersToText(
psiFile: PsiFile ,
diagnostics: Collection ,
diagnosticToExpectedDiagnostic: Map ,
getFileText: (PsiFile ) -> String ,
uncheckedDiagnostics: Collection ,
withNewInferenceDirective: Boolean ,
renderDiagnosticMessages: Boolean
): StringBuffer {
val text = getFileText(psiFile)
val result = StringBuffer ()
val diagnosticsFiltered = diagnostics.filter { actualDiagnostic -> psiFile == actualDiagnostic.file }
if (diagnosticsFiltered.isEmpty() && uncheckedDiagnostics.isEmpty()) {
result.append(text)
return result
}
val diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnosticsFiltered, uncheckedDiagnostics)
if (diagnosticDescriptors.isEmpty()) return result
val opened = Stack ()
val iterator = diagnosticDescriptors.listIterator()
var currentDescriptor: AbstractDiagnosticDescriptor ? = iterator.next()
for (i in 0 until text.length) {
val c = text[i]
while (!opened.isEmpty() && i == opened.peek().end) {
closeDiagnosticString(result)
opened.pop()
}
while (currentDescriptor != null && i == currentDescriptor.start) {
val isSkip = openDiagnosticsString(
result,
currentDescriptor,
diagnosticToExpectedDiagnostic,
withNewInferenceDirective,
renderDiagnosticMessages
)
if (currentDescriptor.end == i && !isSkip)
closeDiagnosticString(result)
else if (!isSkip)
opened.push(currentDescriptor)
currentDescriptor = if (iterator.hasNext()) iterator.next() else null
}
result.append(c)
}
if (currentDescriptor != null ) {
assert(currentDescriptor.start == text.length)
assert(currentDescriptor.end == text.length)
val isSkip = openDiagnosticsString(
result,
currentDescriptor,
diagnosticToExpectedDiagnostic,
withNewInferenceDirective,
renderDiagnosticMessages
)
if (!isSkip)
opened.push(currentDescriptor)
}
while (!opened.isEmpty() && text.length == opened.peek().end) {
closeDiagnosticString(result)
opened.pop()
}
assert(opened.isEmpty()) { "Stack is not empty: $opened" }
return result
}
private fun openDiagnosticsString(
result: StringBuffer ,
currentDescriptor: AbstractDiagnosticDescriptor ,
diagnosticToExpectedDiagnostic: Map ,
withNewInferenceDirective: Boolean ,
renderDiagnosticMessages: Boolean
): Boolean {
var isSkip = true
val diagnosticsAsText = mutableListOf()
when (currentDescriptor) {
is TextDiagnosticDescriptor -> diagnosticsAsText.add(currentDescriptor.textDiagnostic.asString())
is ActualDiagnosticDescriptor -> {
val diagnostics = currentDescriptor.diagnostics
for (diagnostic in diagnostics) {
val expectedDiagnostic = diagnosticToExpectedDiagnostic[diagnostic]
val actualTextDiagnostic = TextDiagnostic .asTextDiagnostic(diagnostic)
if (expectedDiagnostic != null || !hasExplicitDefinitionOnlyOption(diagnostic)) {
val shouldRenderParameters =
renderDiagnosticMessages || expectedDiagnostic?.parameters != null
diagnosticsAsText.add(
actualTextDiagnostic.asString(withNewInferenceDirective, shouldRenderParameters)
)
}
}
}
else -> throw IllegalStateException ("Unknown diagnostic descriptor: $currentDescriptor" )
}
if (diagnosticsAsText.size != 0 ) {
diagnosticsAsText.sort()
result.append("" )
isSkip = false
}
return isSkip
}
private fun closeDiagnosticString(result: StringBuffer ) = result.append("" )
private fun getActualSortedDiagnosticDescriptors(diagnostics: Collection ) =
getSortedDiagnosticDescriptors(diagnostics, emptyList()).filterIsInstance(ActualDiagnosticDescriptor ::class .java )
private fun getSortedDiagnosticDescriptors(
diagnostics: Collection ,
uncheckedDiagnostics: Collection
): List {
val validDiagnostics = diagnostics.filter { actualDiagnostic -> actualDiagnostic.diagnostic.isValid }
val diagnosticDescriptors = groupDiagnosticsByTextRange(validDiagnostics, uncheckedDiagnostics)
diagnosticDescriptors.sortWith(Comparator { d1: AbstractDiagnosticDescriptor , d2: AbstractDiagnosticDescriptor ->
if (d1.start != d2.start) d1.start - d2.start else d2.end - d1.end
})
return diagnosticDescriptors
}
private fun groupDiagnosticsByTextRange(
diagnostics: Collection ,
uncheckedDiagnostics: Collection
): MutableList {
val diagnosticsGroupedByRanges = LinkedListMultimap .create()
for (actualDiagnostic in diagnostics) {
val diagnostic = actualDiagnostic.diagnostic
for (textRange in diagnostic.textRanges) {
diagnosticsGroupedByRanges.put(textRange, actualDiagnostic)
}
}
for ((diagnostic, start, end) in uncheckedDiagnostics) {
val range = TextRange (start, end)
diagnosticsGroupedByRanges.put(range, diagnostic)
}
return diagnosticsGroupedByRanges.keySet().map { range ->
val abstractDiagnostics = diagnosticsGroupedByRanges.get(range)
val needSortingByName =
abstractDiagnostics.any { diagnostic -> diagnostic.inferenceCompatibility != TextDiagnostic .InferenceCompatibility .ALL }
if (needSortingByName) {
abstractDiagnostics.sortBy { it.name }
} else {
abstractDiagnostics.sortBy { it }
}
ActualDiagnosticDescriptor (range.startOffset, range.endOffset, abstractDiagnostics)
}.toMutableList()
}
fun getTypeInfo(
expression: PsiElement ,
bindingContext: BindingContext ,
dataFlowValueFactory: DataFlowValueFactory ?,
languageVersionSettings: LanguageVersionSettings ?,
moduleDescriptor: ModuleDescriptorImpl ?
): Pair ?> {
if (expression is KtCallableDeclaration ) {
val descriptor = bindingContext[BindingContext .DECLARATION_TO_DESCRIPTOR , expression] as? CallableDescriptor
if (descriptor != null ) {
return Pair (descriptor.returnType, null )
}
}
val expressionTypeInfo =
bindingContext[BindingContext .EXPRESSION_TYPE_INFO , expression as KtExpression ] ?: noTypeInfo(DataFlowInfo .EMPTY )
val expressionType = expression.getType(bindingContext)
val result = expressionType ?: return Pair (null , null )
if (dataFlowValueFactory == null || moduleDescriptor == null )
return Pair (expressionType, null )
val dataFlowValue = dataFlowValueFactory.createDataFlowValue(expression, expressionType, bindingContext, moduleDescriptor)
val types = expressionTypeInfo.dataFlowInfo.getStableTypes(dataFlowValue, languageVersionSettings!!)
if (!types.isNullOrEmpty())
return Pair (result, types)
val smartCast = bindingContext[BindingContext .SMARTCAST , expression]
if (smartCast != null && expression is KtReferenceExpression ) {
val declaredType = (bindingContext[BindingContext .REFERENCE_TARGET , expression] as? CallableDescriptor )?.returnType
if (declaredType != null ) {
return Pair (result, setOf(declaredType))
}
}
return Pair (result, null )
}
fun getCallDebugInfo(element: PsiElement , bindingContext: BindingContext ): Pair {
if (element !is KtExpression )
return null to TypeOfCall .OTHER .nameToRender
val call = element.getCall(bindingContext)
val typeOfCall = getTypeOfCall(element, bindingContext)
val fqNameUnsafe = bindingContext[BindingContext .RESOLVED_CALL , call]?.candidateDescriptor?.fqNameUnsafe
return fqNameUnsafe to typeOfCall
}
private fun getTypeOfCall(expression: KtExpression , bindingContext: BindingContext ): String {
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return TypeOfCall .UNRESOLVED .nameToRender
if (resolvedCall is VariableAsFunctionResolvedCall )
return TypeOfCall .VARIABLE_THROUGH_INVOKE .nameToRender
return when (val functionDescriptor = resolvedCall.candidateDescriptor) {
is PropertyDescriptor -> {
TypeOfCall .PROPERTY_GETTER .nameToRender
}
is FunctionDescriptor -> buildString {
if (functionDescriptor.isInline) append("inline " )
if (functionDescriptor.isInfix) append("infix " )
if (functionDescriptor.isOperator) append("operator " )
if (functionDescriptor.isExtension) append("extension " )
append(TypeOfCall .FUNCTION .nameToRender)
}
else -> TypeOfCall .OTHER .nameToRender
}
}
}
enum class TypeOfCall (val nameToRender: String ) {
VARIABLE_THROUGH_INVOKE ("variable&invoke" ),
PROPERTY_GETTER ("variable" ),
FUNCTION ("function" ),
UNRESOLVED ("unresolved" ),
OTHER ("other" )
}