Refactoring: extract DebuggerClassNameProvider
This commit is contained in:
@@ -0,0 +1,342 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
|
import com.intellij.debugger.SourcePosition
|
||||||
|
import com.intellij.debugger.engine.DebugProcess
|
||||||
|
import com.intellij.openapi.application.ModalityState
|
||||||
|
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.project.DumbService
|
||||||
|
import com.intellij.openapi.roots.libraries.LibraryUtil
|
||||||
|
import com.intellij.openapi.ui.MessageType
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||||
|
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||||
|
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
||||||
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||||
|
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||||
|
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.getOrComputeClassNames
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.CachedClassNames
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.NonCachedClassNames
|
||||||
|
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||||
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
|
||||||
|
|
||||||
|
class DebuggerClassNameProvider(val myDebugProcess: DebugProcess, val scopes: List<GlobalSearchScope>) {
|
||||||
|
fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List<String> {
|
||||||
|
val element = sourcePosition.readAction { it.elementAt } ?: return emptyList()
|
||||||
|
val names = classNamesForPosition(element, withInlines)
|
||||||
|
|
||||||
|
val lambdas = findLambdas(sourcePosition)
|
||||||
|
if (lambdas.isEmpty()) {
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
return names + lambdas
|
||||||
|
}
|
||||||
|
|
||||||
|
fun classNamesForPosition(element: PsiElement?, withInlines: Boolean): List<String> {
|
||||||
|
if (DumbService.getInstance(myDebugProcess.project).isDumb) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val baseElement = getElementToCalculateClassName(element) ?: return emptyList()
|
||||||
|
return getOrComputeClassNames(baseElement) { element ->
|
||||||
|
val file = element.readAction { it.containingFile as KtFile }
|
||||||
|
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
|
||||||
|
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||||
|
|
||||||
|
getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findLambdas(sourcePosition: SourcePosition): Collection<String> {
|
||||||
|
val lambdas = sourcePosition.readAction(::getLambdasAtLineIfAny)
|
||||||
|
return lambdas.flatMap { classNamesForPosition(it, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getInternalClassNameForElement(
|
||||||
|
element: KtElement,
|
||||||
|
typeMapper: KotlinTypeMapper,
|
||||||
|
file: KtFile,
|
||||||
|
isInLibrary: Boolean,
|
||||||
|
withInlines: Boolean
|
||||||
|
): KotlinDebuggerCaches.ComputedClassNames {
|
||||||
|
val parent = element.readAction { getElementToCalculateClassName(it.parent) }
|
||||||
|
when (element) {
|
||||||
|
is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper))
|
||||||
|
is KtFunction -> {
|
||||||
|
val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, typeMapper.bindingContext) }
|
||||||
|
if (descriptor != null) {
|
||||||
|
val classNamesForParent = classNamesForPosition(parent, withInlines)
|
||||||
|
if (descriptor.isCrossinline) {
|
||||||
|
return CachedClassNames(classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext))
|
||||||
|
}
|
||||||
|
return CachedClassNames(classNamesForParent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val crossInlineParameterUsages = element.readAction { it.containsCrossInlineParameterUsages(typeMapper.bindingContext) }
|
||||||
|
if (crossInlineParameterUsages.isNotEmpty()) {
|
||||||
|
return CachedClassNames(classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
element is KtFunctionLiteral -> {
|
||||||
|
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
||||||
|
return CachedClassNames(asmType.internalName)
|
||||||
|
}
|
||||||
|
element is KtAnonymousInitializer -> {
|
||||||
|
// Class-object initializer
|
||||||
|
if (parent is KtObjectDeclaration && parent.isCompanion()) {
|
||||||
|
return CachedClassNames(classNamesForPosition(parent.parent, withInlines))
|
||||||
|
}
|
||||||
|
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
||||||
|
}
|
||||||
|
element is KtPropertyAccessor && (!element.readAction { it.property.isTopLevel } || !isInLibrary) -> {
|
||||||
|
val classOrObject = element.readAction { PsiTreeUtil.getParentOfType(it, KtClassOrObject::class.java) }
|
||||||
|
if (classOrObject != null) {
|
||||||
|
return CachedClassNames(getClassNameForClass(classOrObject, typeMapper))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element is KtProperty && (!element.readAction { it.isTopLevel } || !isInLibrary) -> {
|
||||||
|
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||||
|
if (descriptor !is PropertyDescriptor) {
|
||||||
|
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
||||||
|
}
|
||||||
|
|
||||||
|
return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
|
||||||
|
}
|
||||||
|
element is KtNamedFunction -> {
|
||||||
|
val parentInternalName = if (parent is KtClassOrObject) {
|
||||||
|
getClassNameForClass(parent, typeMapper)
|
||||||
|
}
|
||||||
|
else if (parent != null) {
|
||||||
|
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
||||||
|
asmType.internalName
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getClassNameForFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!withInlines) return NonCachedClassNames(parentInternalName)
|
||||||
|
|
||||||
|
val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext)
|
||||||
|
if (parentInternalName == null) return CachedClassNames(inlinedCalls)
|
||||||
|
|
||||||
|
return CachedClassNames(listOf(parentInternalName) + inlinedCalls)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CachedClassNames(getClassNameForFile(file))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getClassNameForClass(klass: KtClassOrObject, typeMapper: KotlinTypeMapper) = klass.readAction { getJvmInternalNameForImpl(typeMapper, it) }
|
||||||
|
private fun getClassNameForFile(file: KtFile) = file.readAction { NoResolveFileClassesProvider.getFileClassInternalName(it) }
|
||||||
|
|
||||||
|
private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out KtElement>> =
|
||||||
|
arrayOf(KtClass::class.java,
|
||||||
|
KtObjectDeclaration::class.java,
|
||||||
|
KtEnumEntry::class.java,
|
||||||
|
KtFunctionLiteral::class.java,
|
||||||
|
KtNamedFunction::class.java,
|
||||||
|
KtPropertyAccessor::class.java,
|
||||||
|
KtProperty::class.java,
|
||||||
|
KtClassInitializer::class.java)
|
||||||
|
|
||||||
|
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? {
|
||||||
|
if (notPositionedElement?.javaClass as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return notPositionedElement as KtElement
|
||||||
|
|
||||||
|
return readAction { PsiTreeUtil.getParentOfType(notPositionedElement, *TYPES_TO_CALCULATE_CLASSNAME) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getJvmInternalNameForPropertyOwner(typeMapper: KotlinTypeMapper, descriptor: PropertyDescriptor): String {
|
||||||
|
return descriptor.readAction {
|
||||||
|
typeMapper.mapOwner(
|
||||||
|
if (JvmAbi.isPropertyWithBackingFieldInOuterClass(it)) it.containingDeclaration else it
|
||||||
|
).internalName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getJvmInternalNameForImpl(typeMapper: KotlinTypeMapper, ktClass: KtClassOrObject): String? {
|
||||||
|
val classDescriptor = typeMapper.bindingContext.get<PsiElement, ClassDescriptor>(BindingContext.CLASS, ktClass) ?: return null
|
||||||
|
|
||||||
|
if (ktClass is KtClass && ktClass.isInterface()) {
|
||||||
|
return typeMapper.mapDefaultImpls(classDescriptor).internalName
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeMapper.mapClass(classDescriptor).internalName
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): List<String> {
|
||||||
|
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val searchResult = hashSetOf<KtElement>()
|
||||||
|
val functionName = function.readAction { it.name }
|
||||||
|
|
||||||
|
val task = Runnable {
|
||||||
|
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
||||||
|
if (!it.readAction { it.isImportUsage() }) {
|
||||||
|
val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) }
|
||||||
|
if (usage != null) {
|
||||||
|
searchResult.add(usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSuccess = true
|
||||||
|
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||||
|
if (!applicationEx.holdsReadLock() || applicationEx.isDispatchThread) {
|
||||||
|
applicationEx.invokeAndWait(
|
||||||
|
{
|
||||||
|
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||||
|
task,
|
||||||
|
"Compute class names for function $functionName",
|
||||||
|
true,
|
||||||
|
myDebugProcess.project)
|
||||||
|
}, ModalityState.NON_MODAL)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Pooled thread with read lock. Can't invoke task under UI progress, so call it directly.
|
||||||
|
task.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSuccess) {
|
||||||
|
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
||||||
|
"Debugger can skip some executions of $functionName method, because the computation of class names was interrupted", MessageType.WARNING
|
||||||
|
).notify(myDebugProcess.project)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO recursive search
|
||||||
|
return searchResult.flatMap { classNamesForPosition(it, true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findCrossInlineArguments(argument: KtFunction, parameterDescriptor: ValueParameterDescriptor, context: BindingContext): Set<String> {
|
||||||
|
return runReadAction {
|
||||||
|
val source = parameterDescriptor.original.source.getPsi() as? KtParameter
|
||||||
|
val functionName = source?.ownerFunction?.name
|
||||||
|
if (functionName != null) {
|
||||||
|
return@runReadAction setOf(getCrossInlineArgumentClassName(argument, functionName, context))
|
||||||
|
}
|
||||||
|
return@runReadAction emptySet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCrossInlineArgumentClassName(argument: KtFunction, inlineFunctionName: String, context: BindingContext): String {
|
||||||
|
val anonymousClassNameForArgument = CodegenBinding.asmTypeForAnonymousClass(context, argument).internalName
|
||||||
|
val newName = anonymousClassNameForArgument.substringIndex() + InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX + "$" + inlineFunctionName
|
||||||
|
return "$newName$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtElement.containsCrossInlineParameterUsages(context: BindingContext): Collection<ValueParameterDescriptor> {
|
||||||
|
fun KtElement.hasParameterCall(parameter: KtParameter): Boolean {
|
||||||
|
return ReferencesSearch.search(parameter).any {
|
||||||
|
this.textRange.contains(it.element.textRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val inlineFunction = this.parents.firstIsInstanceOrNull<KtNamedFunction>() ?: return emptySet()
|
||||||
|
|
||||||
|
val inlineFunctionDescriptor = context[BindingContext.FUNCTION, inlineFunction]
|
||||||
|
if (inlineFunctionDescriptor == null || !InlineUtil.isInline(inlineFunctionDescriptor)) return emptySet()
|
||||||
|
|
||||||
|
return inlineFunctionDescriptor.valueParameters
|
||||||
|
.filter { it.isCrossinline }
|
||||||
|
.mapNotNull {
|
||||||
|
val psiParameter = it.source.getPsi() as? KtParameter
|
||||||
|
if (psiParameter != null && this@containsCrossInlineParameterUsages.hasParameterCall(psiParameter))
|
||||||
|
it
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun classNamesForCrossInlineParameters(usedParameters: Collection<ValueParameterDescriptor>, context: BindingContext): Set<String> {
|
||||||
|
// We could calculate className only for one of parameters, because we add '*' to match all crossInlined parameter calls
|
||||||
|
val parameter = usedParameters.first()
|
||||||
|
val result = hashSetOf<String>()
|
||||||
|
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
|
||||||
|
|
||||||
|
ReferencesSearch.search(inlineFunction, getScopeForInlineFunctionUsages(inlineFunction)).forEach {
|
||||||
|
runReadAction {
|
||||||
|
if (!it.isImportUsage()) {
|
||||||
|
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
|
||||||
|
if (call != null) {
|
||||||
|
val resolvedCall = call.getResolvedCall(context)
|
||||||
|
val argument = resolvedCall?.valueArguments?.entries?.firstOrNull { it.key.original == parameter }?.value
|
||||||
|
if (argument != null) {
|
||||||
|
val argumentExpression = getArgumentExpression(argument.arguments.first())
|
||||||
|
if (argumentExpression is KtFunction) {
|
||||||
|
result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope {
|
||||||
|
val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile }
|
||||||
|
if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||||
|
return GlobalSearchScope.union(scopes.toTypedArray())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return myDebugProcess.searchScope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||||
|
|
||||||
|
private fun String.substringIndex(): String {
|
||||||
|
if (lastIndexOf("$") < 0) return this
|
||||||
|
|
||||||
|
val suffix = substringAfterLast("$")
|
||||||
|
if (suffix.all(Char::isDigit)) {
|
||||||
|
return substringBeforeLast("$") + "$"
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,65 +25,41 @@ import com.intellij.debugger.engine.PositionManagerEx
|
|||||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||||
import com.intellij.openapi.application.ModalityState
|
|
||||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
|
||||||
import com.intellij.openapi.progress.ProgressManager
|
|
||||||
import com.intellij.openapi.project.DumbService
|
|
||||||
import com.intellij.openapi.roots.libraries.LibraryUtil
|
|
||||||
import com.intellij.openapi.ui.MessageType
|
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import com.intellij.util.ThreeState
|
import com.intellij.util.ThreeState
|
||||||
import com.intellij.xdebugger.frame.XStackFrame
|
import com.intellij.xdebugger.frame.XStackFrame
|
||||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
|
||||||
import com.sun.jdi.AbsentInformationException
|
import com.sun.jdi.AbsentInformationException
|
||||||
import com.sun.jdi.Location
|
import com.sun.jdi.Location
|
||||||
import com.sun.jdi.ReferenceType
|
import com.sun.jdi.ReferenceType
|
||||||
import com.sun.jdi.request.ClassPrepareRequest
|
import com.sun.jdi.request.ClassPrepareRequest
|
||||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
|
||||||
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
|
||||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||||
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
|
||||||
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
|
|
||||||
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.getOrComputeClassNames
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.CachedClassNames
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.NonCachedClassNames
|
|
||||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
import org.jetbrains.kotlin.idea.util.DebuggerUtils
|
import org.jetbrains.kotlin.idea.util.DebuggerUtils
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.psi.KtFunction
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.psi.KtParameter
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
|
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
|
||||||
|
|
||||||
class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() {
|
class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() {
|
||||||
|
|
||||||
private val scopes = listOf(
|
private val scopes: List<GlobalSearchScope> = listOf(
|
||||||
myDebugProcess.searchScope,
|
myDebugProcess.searchScope,
|
||||||
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project)
|
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project)
|
||||||
)
|
)
|
||||||
@@ -184,7 +160,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val internalClassNames = classNamesForPosition(literal.firstChild, true)
|
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(literal.firstChild, true)
|
||||||
if (internalClassNames.any { it == currentLocationClassName }) {
|
if (internalClassNames.any { it == currentLocationClassName }) {
|
||||||
return literal
|
return literal
|
||||||
}
|
}
|
||||||
@@ -241,7 +217,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
|
|
||||||
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return result
|
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return result
|
||||||
|
|
||||||
val names = classNamesForPosition(sourcePosition, true)
|
val names = DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(sourcePosition, true)
|
||||||
for (name in names) {
|
for (name in names) {
|
||||||
result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name))
|
result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name))
|
||||||
}
|
}
|
||||||
@@ -261,39 +237,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun originalClassNameForPosition(sourcePosition: SourcePosition): String? {
|
fun originalClassNameForPosition(sourcePosition: SourcePosition): String? {
|
||||||
return classNamesForPosition(sourcePosition, false).firstOrNull()
|
return DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(sourcePosition, false).firstOrNull()
|
||||||
}
|
|
||||||
|
|
||||||
private fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List<String> {
|
|
||||||
val element = sourcePosition.readAction { it.elementAt } ?: return emptyList()
|
|
||||||
val names = classNamesForPosition(element, withInlines)
|
|
||||||
|
|
||||||
val lambdas = findLambdas(sourcePosition)
|
|
||||||
if (lambdas.isEmpty()) {
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
return names + lambdas
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun classNamesForPosition(element: PsiElement?, withInlines: Boolean): List<String> {
|
|
||||||
if (DumbService.getInstance(myDebugProcess.project).isDumb) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val baseElement = getElementToCalculateClassName(element) ?: return emptyList()
|
|
||||||
return getOrComputeClassNames(baseElement) { element ->
|
|
||||||
val file = element.readAction { it.containingFile as KtFile }
|
|
||||||
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
|
|
||||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
|
||||||
|
|
||||||
getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findLambdas(sourcePosition: SourcePosition): Collection<String> {
|
|
||||||
val lambdas = sourcePosition.readAction { getLambdasAtLineIfAny(it) }
|
|
||||||
return lambdas.flatMap { classNamesForPosition(it, true) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
|
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
|
||||||
@@ -324,266 +268,15 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
throw NoDataException.INSTANCE
|
throw NoDataException.INSTANCE
|
||||||
}
|
}
|
||||||
|
|
||||||
return classNamesForPosition(position, true).mapNotNull {
|
return DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(position, true).mapNotNull {
|
||||||
className ->
|
className ->
|
||||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, className.replace('/', '.'))
|
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, className.replace('/', '.'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInternalClassNameForElement(
|
|
||||||
element: KtElement,
|
|
||||||
typeMapper: KotlinTypeMapper,
|
|
||||||
file: KtFile,
|
|
||||||
isInLibrary: Boolean,
|
|
||||||
withInlines: Boolean
|
|
||||||
): KotlinDebuggerCaches.ComputedClassNames {
|
|
||||||
val parent = element.readAction { getElementToCalculateClassName(it.parent) }
|
|
||||||
when (element) {
|
|
||||||
is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper))
|
|
||||||
is KtFunction -> {
|
|
||||||
val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, typeMapper.bindingContext) }
|
|
||||||
if (descriptor != null) {
|
|
||||||
val classNamesForParent = classNamesForPosition(parent, withInlines)
|
|
||||||
if (descriptor.isCrossinline) {
|
|
||||||
return CachedClassNames(classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext))
|
|
||||||
}
|
|
||||||
return CachedClassNames(classNamesForParent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val crossInlineParameterUsages = element.readAction { it.containsCrossInlineParameterUsages(typeMapper.bindingContext) }
|
|
||||||
if (crossInlineParameterUsages.isNotEmpty()) {
|
|
||||||
return CachedClassNames(classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
element is KtFunctionLiteral -> {
|
|
||||||
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
|
||||||
return CachedClassNames(asmType.internalName)
|
|
||||||
}
|
|
||||||
element is KtAnonymousInitializer -> {
|
|
||||||
// Class-object initializer
|
|
||||||
if (parent is KtObjectDeclaration && parent.isCompanion()) {
|
|
||||||
return CachedClassNames(classNamesForPosition(parent.parent, withInlines))
|
|
||||||
}
|
|
||||||
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
|
||||||
}
|
|
||||||
element is KtPropertyAccessor && (!element.readAction { it.property.isTopLevel } || !isInLibrary) -> {
|
|
||||||
val classOrObject = element.readAction { PsiTreeUtil.getParentOfType(it, KtClassOrObject::class.java) }
|
|
||||||
if (classOrObject != null) {
|
|
||||||
return CachedClassNames(getClassNameForClass(classOrObject, typeMapper))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element is KtProperty && (!element.readAction { it.isTopLevel } || !isInLibrary) -> {
|
|
||||||
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
|
||||||
if (descriptor !is PropertyDescriptor) {
|
|
||||||
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
|
||||||
}
|
|
||||||
|
|
||||||
return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
|
|
||||||
}
|
|
||||||
element is KtNamedFunction -> {
|
|
||||||
val parentInternalName = if (parent is KtClassOrObject) {
|
|
||||||
getClassNameForClass(parent, typeMapper)
|
|
||||||
}
|
|
||||||
else if (parent != null) {
|
|
||||||
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
|
||||||
asmType.internalName
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
getClassNameForFile(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!withInlines) return NonCachedClassNames(parentInternalName)
|
|
||||||
|
|
||||||
val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext)
|
|
||||||
if (parentInternalName == null) return CachedClassNames(inlinedCalls)
|
|
||||||
|
|
||||||
return CachedClassNames(listOf(parentInternalName) + inlinedCalls)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CachedClassNames(getClassNameForFile(file))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getClassNameForClass(klass: KtClassOrObject, typeMapper: KotlinTypeMapper) = klass.readAction { getJvmInternalNameForImpl(typeMapper, it) }
|
|
||||||
private fun getClassNameForFile(file: KtFile) = file.readAction { NoResolveFileClassesProvider.getFileClassInternalName(it) }
|
|
||||||
|
|
||||||
private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out KtElement>> =
|
|
||||||
arrayOf(KtClass::class.java,
|
|
||||||
KtObjectDeclaration::class.java,
|
|
||||||
KtEnumEntry::class.java,
|
|
||||||
KtFunctionLiteral::class.java,
|
|
||||||
KtNamedFunction::class.java,
|
|
||||||
KtPropertyAccessor::class.java,
|
|
||||||
KtProperty::class.java,
|
|
||||||
KtClassInitializer::class.java)
|
|
||||||
|
|
||||||
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? {
|
|
||||||
if (notPositionedElement?.javaClass as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return notPositionedElement as KtElement
|
|
||||||
|
|
||||||
return readAction { PsiTreeUtil.getParentOfType(notPositionedElement, *TYPES_TO_CALCULATE_CLASSNAME) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getJvmInternalNameForPropertyOwner(typeMapper: KotlinTypeMapper, descriptor: PropertyDescriptor): String {
|
|
||||||
return descriptor.readAction {
|
|
||||||
typeMapper.mapOwner(
|
|
||||||
if (JvmAbi.isPropertyWithBackingFieldInOuterClass(it)) it.containingDeclaration else it
|
|
||||||
).internalName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getJvmInternalNameForImpl(typeMapper: KotlinTypeMapper, ktClass: KtClassOrObject): String? {
|
|
||||||
val classDescriptor = typeMapper.bindingContext.get<PsiElement, ClassDescriptor>(BindingContext.CLASS, ktClass) ?: return null
|
|
||||||
|
|
||||||
if (ktClass is KtClass && ktClass.isInterface()) {
|
|
||||||
return typeMapper.mapDefaultImpls(classDescriptor).internalName
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeMapper.mapClass(classDescriptor).internalName
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): List<String> {
|
|
||||||
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val searchResult = hashSetOf<KtElement>()
|
|
||||||
val functionName = function.readAction { it.name }
|
|
||||||
|
|
||||||
val task = Runnable {
|
|
||||||
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
|
||||||
if (!it.readAction { it.isImportUsage() }) {
|
|
||||||
val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) }
|
|
||||||
if (usage != null) {
|
|
||||||
searchResult.add(usage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isSuccess = true
|
|
||||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
|
||||||
if (!applicationEx.holdsReadLock() || applicationEx.isDispatchThread) {
|
|
||||||
applicationEx.invokeAndWait(
|
|
||||||
{
|
|
||||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
|
||||||
task,
|
|
||||||
"Compute class names for function $functionName",
|
|
||||||
true,
|
|
||||||
myDebugProcess.project)
|
|
||||||
}, ModalityState.NON_MODAL)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Pooled thread with read lock. Can't invoke task under UI progress, so call it directly.
|
|
||||||
task.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isSuccess) {
|
|
||||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
|
||||||
"Debugger can skip some executions of $functionName method, because the computation of class names was interrupted", MessageType.WARNING
|
|
||||||
).notify(myDebugProcess.project)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO recursive search
|
|
||||||
return searchResult.flatMap { classNamesForPosition(it, true) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findCrossInlineArguments(argument: KtFunction, parameterDescriptor: ValueParameterDescriptor, context: BindingContext): Set<String> {
|
|
||||||
return runReadAction {
|
|
||||||
val source = parameterDescriptor.original.source.getPsi() as? KtParameter
|
|
||||||
val functionName = source?.ownerFunction?.name
|
|
||||||
if (functionName != null) {
|
|
||||||
return@runReadAction setOf(getCrossInlineArgumentClassName(argument, functionName, context))
|
|
||||||
}
|
|
||||||
return@runReadAction emptySet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCrossInlineArgumentClassName(argument: KtFunction, inlineFunctionName: String, context: BindingContext): String {
|
|
||||||
val anonymousClassNameForArgument = CodegenBinding.asmTypeForAnonymousClass(context, argument).internalName
|
|
||||||
val newName = anonymousClassNameForArgument.substringIndex() + InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX + "$" + inlineFunctionName
|
|
||||||
return "$newName$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KtElement.containsCrossInlineParameterUsages(context: BindingContext): Collection<ValueParameterDescriptor> {
|
|
||||||
fun KtElement.hasParameterCall(parameter: KtParameter): Boolean {
|
|
||||||
return ReferencesSearch.search(parameter).any {
|
|
||||||
this.textRange.contains(it.element.textRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val inlineFunction = this.parents.firstIsInstanceOrNull<KtNamedFunction>() ?: return emptySet()
|
|
||||||
|
|
||||||
val inlineFunctionDescriptor = context[BindingContext.FUNCTION, inlineFunction]
|
|
||||||
if (inlineFunctionDescriptor == null || !InlineUtil.isInline(inlineFunctionDescriptor)) return emptySet()
|
|
||||||
|
|
||||||
return inlineFunctionDescriptor.valueParameters
|
|
||||||
.filter { it.isCrossinline }
|
|
||||||
.mapNotNull {
|
|
||||||
val psiParameter = it.source.getPsi() as? KtParameter
|
|
||||||
if (psiParameter != null && this@containsCrossInlineParameterUsages.hasParameterCall(psiParameter))
|
|
||||||
it
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun classNamesForCrossInlineParameters(usedParameters: Collection<ValueParameterDescriptor>, context: BindingContext): Set<String> {
|
|
||||||
// We could calculate className only for one of parameters, because we add '*' to match all crossInlined parameter calls
|
|
||||||
val parameter = usedParameters.first()
|
|
||||||
val result = hashSetOf<String>()
|
|
||||||
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
|
|
||||||
|
|
||||||
ReferencesSearch.search(inlineFunction, getScopeForInlineFunctionUsages(inlineFunction)).forEach {
|
|
||||||
runReadAction {
|
|
||||||
if (!it.isImportUsage()) {
|
|
||||||
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
|
|
||||||
if (call != null) {
|
|
||||||
val resolvedCall = call.getResolvedCall(context)
|
|
||||||
val argument = resolvedCall?.valueArguments?.entries?.firstOrNull { it.key.original == parameter }?.value
|
|
||||||
if (argument != null) {
|
|
||||||
val argumentExpression = getArgumentExpression(argument.arguments.first())
|
|
||||||
if (argumentExpression is KtFunction) {
|
|
||||||
result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope {
|
|
||||||
val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile }
|
|
||||||
if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
|
||||||
return GlobalSearchScope.union(scopes.toTypedArray())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return myDebugProcess.searchScope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
|
||||||
|
|
||||||
private fun String.substringIndex(): String {
|
|
||||||
if (lastIndexOf("$") < 0) return this
|
|
||||||
|
|
||||||
val suffix = substringAfterLast("$")
|
|
||||||
if (suffix.all { it.isDigit() }) {
|
|
||||||
return substringBeforeLast("$") + "$"
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin")
|
private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||||
return runReadAction { f(this) }
|
return runReadAction { f(this) }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user