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.Cached
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.NonCached
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.NonCached
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
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.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
@@ -137,38 +138,52 @@ class DebuggerClassNameProvider(
|
|||||||
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
|
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
|
||||||
else
|
else
|
||||||
ComputedClassNames.EMPTY
|
ComputedClassNames.EMPTY
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
getNameForNonLocalClass(it)?.let { ComputedClassNames.Cached(it) } ?: ComputedClassNames.EMPTY
|
getNameForNonLocalClass(it)?.let { ComputedClassNames.Cached(it) } ?: ComputedClassNames.EMPTY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is KtProperty -> {
|
is KtProperty -> {
|
||||||
// inline top level property!
|
val nonInlineClasses = if (runReadAction { element.isTopLevel }) {
|
||||||
if (runReadAction { element.isTopLevel }) {
|
// Top level property
|
||||||
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
|
|
||||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
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 -> {
|
is KtNamedFunction -> {
|
||||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||||
@@ -180,25 +195,23 @@ class DebuggerClassNameProvider(
|
|||||||
if (runReadAction { element.name == null || element.isLocal } || isFunctionWithSuspendStateMachine(descriptor, typeMapper.bindingContext)) {
|
if (runReadAction { element.name == null || element.isLocal } || isFunctionWithSuspendStateMachine(descriptor, typeMapper.bindingContext)) {
|
||||||
nonInlineClasses = classNamesOfContainingDeclaration + ComputedClassNames.Cached(
|
nonInlineClasses = classNamesOfContainingDeclaration + ComputedClassNames.Cached(
|
||||||
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName())
|
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName())
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
nonInlineClasses = classNamesOfContainingDeclaration
|
nonInlineClasses = classNamesOfContainingDeclaration
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!findInlineUseSites) {
|
if (!findInlineUseSites || !element.isInlineInReadAction) {
|
||||||
return NonCached(nonInlineClasses.classNames)
|
return NonCached(nonInlineClasses.classNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(
|
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||||
element,
|
|
||||||
KotlinDebuggerCaches.getOrCreateTypeMapper(element).bindingContext
|
|
||||||
) { this.getOuterClassNamesForElement(it) }
|
|
||||||
|
|
||||||
nonInlineClasses + inlineCallSiteClasses
|
nonInlineClasses + inlineCallSiteClasses
|
||||||
}
|
}
|
||||||
is KtAnonymousInitializer -> {
|
is KtAnonymousInitializer -> {
|
||||||
val initializerOwner = runReadAction { element.containingDeclaration }
|
val initializerOwner = runReadAction { element.containingDeclaration }
|
||||||
|
|
||||||
if (initializerOwner is KtObjectDeclaration && runReadAction { initializerOwner.isCompanion() }) {
|
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
|
||||||
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +250,12 @@ class DebuggerClassNameProvider(
|
|||||||
return descriptor is SimpleFunctionDescriptor && descriptor.isSuspend && descriptor.containsNonTailSuspensionCalls(bindingContext)
|
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
|
private val PsiElement.relevantParentInReadAction
|
||||||
get() = runReadAction { getRelevantElement(this.parent) }
|
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.GlobalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
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.search.usagesSearch.isImportUsage
|
||||||
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
|
||||||
@@ -32,19 +34,24 @@ import org.jetbrains.kotlin.psi.*
|
|||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
import org.jetbrains.kotlin.idea.debugger.DebuggerClassNameProvider.Companion.getRelevantElement
|
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
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||||
|
|
||||||
class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: List<GlobalSearchScope>) {
|
class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: List<GlobalSearchScope>) {
|
||||||
fun findInlinedCalls(function: KtNamedFunction, context: BindingContext, transformer: (PsiElement) -> ComputedClassNames): ComputedClassNames {
|
fun findInlinedCalls(
|
||||||
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
|
declaration: KtDeclaration,
|
||||||
|
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(declaration).bindingContext,
|
||||||
|
transformer: (PsiElement) -> ComputedClassNames
|
||||||
|
): ComputedClassNames {
|
||||||
|
if (!checkIfInline(declaration, bindingContext)) {
|
||||||
return ComputedClassNames.EMPTY
|
return ComputedClassNames.EMPTY
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val searchResult = hashSetOf<PsiElement>()
|
val searchResult = hashSetOf<PsiElement>()
|
||||||
val functionName = runReadAction { function.name }
|
val declarationName = runReadAction { declaration.name }
|
||||||
|
|
||||||
val task = Runnable {
|
val task = Runnable {
|
||||||
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach {
|
||||||
if (!runReadAction { it.isImportUsage() }) {
|
if (!runReadAction { it.isImportUsage() }) {
|
||||||
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
||||||
if (usage != null) {
|
if (usage != null) {
|
||||||
@@ -61,7 +68,7 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
|
|||||||
{
|
{
|
||||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||||
task,
|
task,
|
||||||
"Compute class names for function $functionName",
|
"Compute class names for declaration $declarationName",
|
||||||
true,
|
true,
|
||||||
myDebugProcess.project)
|
myDebugProcess.project)
|
||||||
}, ModalityState.NON_MODAL)
|
}, ModalityState.NON_MODAL)
|
||||||
@@ -73,7 +80,8 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
|
|||||||
|
|
||||||
if (!isSuccess) {
|
if (!isSuccess) {
|
||||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
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)
|
).notify(myDebugProcess.project)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +90,17 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope {
|
private fun checkIfInline(declaration: KtDeclaration, bindingContext: BindingContext): Boolean {
|
||||||
val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile }
|
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)) {
|
if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||||
return GlobalSearchScope.union(scopes.toTypedArray())
|
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);
|
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")
|
@TestMetadata("kt15823.kt")
|
||||||
public void testKt15823() throws Exception {
|
public void testKt15823() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user