Support inline properties in debugger class search
This commit is contained in:
committed by
Yan Zhulanow
parent
cb9e90183a
commit
9e61eea758
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companio
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.Cached
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.NonCached
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
@@ -137,38 +138,52 @@ class DebuggerClassNameProvider(
|
||||
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
|
||||
else
|
||||
ComputedClassNames.EMPTY
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
getNameForNonLocalClass(it)?.let { ComputedClassNames.Cached(it) } ?: ComputedClassNames.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is KtProperty -> {
|
||||
// inline top level property!
|
||||
if (runReadAction { element.isTopLevel }) {
|
||||
return getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
if (enclosingElementForLocal != null) {
|
||||
return getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
}
|
||||
|
||||
// Handle non-local property
|
||||
val containingClassOrFile = runReadAction {
|
||||
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
|
||||
}
|
||||
|
||||
if (containingClassOrFile is KtObjectDeclaration && runReadAction { containingClassOrFile.isCompanion() }) {
|
||||
// Properties from the companion object can be placed in the companion object's containing class
|
||||
return (getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) +
|
||||
getOuterClassNamesForElement(containingClassOrFile)).distinct()
|
||||
}
|
||||
|
||||
if (containingClassOrFile != null)
|
||||
getOuterClassNamesForElement(containingClassOrFile)
|
||||
else
|
||||
val nonInlineClasses = if (runReadAction { element.isTopLevel }) {
|
||||
// Top level property
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
else {
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
if (enclosingElementForLocal != null) {
|
||||
// Local class
|
||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
}
|
||||
else {
|
||||
val containingClassOrFile = runReadAction {
|
||||
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
|
||||
}
|
||||
|
||||
if (containingClassOrFile is KtObjectDeclaration && containingClassOrFile.isCompanionInReadAction) {
|
||||
// Properties from the companion object can be placed in the companion object's containing class
|
||||
(getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) +
|
||||
getOuterClassNamesForElement(containingClassOrFile)).distinct()
|
||||
}
|
||||
else if (containingClassOrFile != null) {
|
||||
getOuterClassNamesForElement(containingClassOrFile)
|
||||
}
|
||||
else {
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (findInlineUseSites && (
|
||||
element.isInlineInReadAction ||
|
||||
runReadAction { element.accessors.any { it.hasModifier(KtTokens.INLINE_KEYWORD) } })
|
||||
) {
|
||||
nonInlineClasses + inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||
}
|
||||
else {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
}
|
||||
is KtNamedFunction -> {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
@@ -180,25 +195,23 @@ class DebuggerClassNameProvider(
|
||||
if (runReadAction { element.name == null || element.isLocal } || isFunctionWithSuspendStateMachine(descriptor, typeMapper.bindingContext)) {
|
||||
nonInlineClasses = classNamesOfContainingDeclaration + ComputedClassNames.Cached(
|
||||
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName())
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
nonInlineClasses = classNamesOfContainingDeclaration
|
||||
}
|
||||
|
||||
if (!findInlineUseSites) {
|
||||
if (!findInlineUseSites || !element.isInlineInReadAction) {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
|
||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(
|
||||
element,
|
||||
KotlinDebuggerCaches.getOrCreateTypeMapper(element).bindingContext
|
||||
) { this.getOuterClassNamesForElement(it) }
|
||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||
|
||||
nonInlineClasses + inlineCallSiteClasses
|
||||
}
|
||||
is KtAnonymousInitializer -> {
|
||||
val initializerOwner = runReadAction { element.containingDeclaration }
|
||||
|
||||
if (initializerOwner is KtObjectDeclaration && runReadAction { initializerOwner.isCompanion() }) {
|
||||
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
|
||||
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
||||
}
|
||||
|
||||
@@ -237,6 +250,12 @@ class DebuggerClassNameProvider(
|
||||
return descriptor is SimpleFunctionDescriptor && descriptor.isSuspend && descriptor.containsNonTailSuspensionCalls(bindingContext)
|
||||
}
|
||||
|
||||
private val KtDeclaration.isInlineInReadAction: Boolean
|
||||
get() = runReadAction { hasModifier(KtTokens.INLINE_KEYWORD) }
|
||||
|
||||
private val KtObjectDeclaration.isCompanionInReadAction: Boolean
|
||||
get() = runReadAction { isCompanion() }
|
||||
|
||||
private val PsiElement.relevantParentInReadAction
|
||||
get() = runReadAction { getRelevantElement(this.parent) }
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
@@ -32,19 +34,24 @@ import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerClassNameProvider.Companion.getRelevantElement
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
|
||||
class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: List<GlobalSearchScope>) {
|
||||
fun findInlinedCalls(function: KtNamedFunction, context: BindingContext, transformer: (PsiElement) -> ComputedClassNames): ComputedClassNames {
|
||||
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
|
||||
fun findInlinedCalls(
|
||||
declaration: KtDeclaration,
|
||||
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(declaration).bindingContext,
|
||||
transformer: (PsiElement) -> ComputedClassNames
|
||||
): ComputedClassNames {
|
||||
if (!checkIfInline(declaration, bindingContext)) {
|
||||
return ComputedClassNames.EMPTY
|
||||
}
|
||||
else {
|
||||
val searchResult = hashSetOf<PsiElement>()
|
||||
val functionName = runReadAction { function.name }
|
||||
val declarationName = runReadAction { declaration.name }
|
||||
|
||||
val task = Runnable {
|
||||
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
||||
ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach {
|
||||
if (!runReadAction { it.isImportUsage() }) {
|
||||
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
||||
if (usage != null) {
|
||||
@@ -61,7 +68,7 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
|
||||
{
|
||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
task,
|
||||
"Compute class names for function $functionName",
|
||||
"Compute class names for declaration $declarationName",
|
||||
true,
|
||||
myDebugProcess.project)
|
||||
}, ModalityState.NON_MODAL)
|
||||
@@ -73,7 +80,8 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
|
||||
|
||||
if (!isSuccess) {
|
||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
||||
"Debugger can skip some executions of $functionName method, because the computation of class names was interrupted", MessageType.WARNING
|
||||
"Debugger can skip some executions of $declarationName because the computation of class names was interrupted",
|
||||
MessageType.WARNING
|
||||
).notify(myDebugProcess.project)
|
||||
}
|
||||
|
||||
@@ -82,8 +90,17 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope {
|
||||
val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile }
|
||||
private fun checkIfInline(declaration: KtDeclaration, bindingContext: BindingContext): Boolean {
|
||||
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) ?: return false
|
||||
return when (descriptor) {
|
||||
is FunctionDescriptor -> InlineUtil.isInline(descriptor)
|
||||
is PropertyDescriptor -> InlineUtil.hasInlineAccessors(descriptor)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScopeForInlineDeclarationUsages(inlineDeclaration: KtDeclaration): GlobalSearchScope {
|
||||
val virtualFile = runReadAction { inlineDeclaration.containingFile.virtualFile }
|
||||
if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||
return GlobalSearchScope.union(scopes.toTypedArray())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
LineBreakpoint created at inlineProperties.kt:8
|
||||
LineBreakpoint created at inlineProperties.kt:20
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
inlineProperties.kt:8
|
||||
inlineProperties.kt:20
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -0,0 +1,9 @@
|
||||
LineBreakpoint created at inlinePropertyAccessors.kt:8
|
||||
LineBreakpoint created at inlinePropertyAccessors.kt:20
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
inlinePropertyAccessors.kt:8
|
||||
inlinePropertyAccessors.kt:20
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -0,0 +1,24 @@
|
||||
package inlineProperties
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
class A {
|
||||
inline val a: String
|
||||
get() {
|
||||
//Breakpoint!
|
||||
return System.nanoTime().toString()
|
||||
}
|
||||
}
|
||||
|
||||
A().apply { a }
|
||||
B().apply { b }
|
||||
}
|
||||
|
||||
class B {
|
||||
inline val b: String
|
||||
get() {
|
||||
//Breakpoint!
|
||||
return System.nanoTime().toString()
|
||||
}
|
||||
}
|
||||
|
||||
// RESUME: 2
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package inlinePropertyAccessors
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
class A {
|
||||
val a: String
|
||||
inline get() {
|
||||
//Breakpoint!
|
||||
return System.nanoTime().toString()
|
||||
}
|
||||
}
|
||||
|
||||
A().apply { a }
|
||||
B().apply { b }
|
||||
}
|
||||
|
||||
class B {
|
||||
val b: String
|
||||
inline get() {
|
||||
//Breakpoint!
|
||||
return System.nanoTime().toString()
|
||||
}
|
||||
}
|
||||
|
||||
// RESUME: 2
|
||||
@@ -1070,6 +1070,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineProperties.kt")
|
||||
public void testInlineProperties() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/inlineProperties.kt");
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlinePropertyAccessors.kt")
|
||||
public void testInlinePropertyAccessors() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/inlinePropertyAccessors.kt");
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt15823.kt")
|
||||
public void testKt15823() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt");
|
||||
|
||||
Reference in New Issue
Block a user