Evaluator: Handle function context gracefully. Use file scope as a fallback instead of error scope
This commit is contained in:
+23
-14
@@ -49,8 +49,8 @@ class CodeFragmentAnalyzer(
|
|||||||
return doAnalyzeCodeFragment(codeFragment, contextAnalysisResult)
|
return doAnalyzeCodeFragment(codeFragment, contextAnalysisResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextAnalysisResult: ContextAnalysisResult): BindingTrace {
|
private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextInfo: ContextInfo): BindingTrace {
|
||||||
val (bindingContext, scope, dataFlowInfo) = contextAnalysisResult
|
val (bindingContext, scope, dataFlowInfo) = contextInfo
|
||||||
val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis")
|
val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis")
|
||||||
|
|
||||||
when (val contentElement = codeFragment.getContentElement()) {
|
when (val contentElement = codeFragment.getContentElement()) {
|
||||||
@@ -79,19 +79,19 @@ class CodeFragmentAnalyzer(
|
|||||||
return bindingTrace
|
return bindingTrace
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class ContextAnalysisResult(
|
private data class ContextInfo(val bindingContext: BindingContext, val scope: LexicalScope, val dataFlowInfo: DataFlowInfo)
|
||||||
val bindingContext: BindingContext,
|
|
||||||
val scope: LexicalScope,
|
|
||||||
val dataFlowInfo: DataFlowInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextAnalysisResult {
|
private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextInfo {
|
||||||
fun resolutionFactory(element: KtElement): BindingContext {
|
fun resolutionFactory(element: KtElement): BindingContext {
|
||||||
return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode)
|
return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = refineContextElement(codeFragment.context)
|
val context = refineContextElement(codeFragment.context)
|
||||||
|
val info = getContextInfo(context, ::resolutionFactory)
|
||||||
|
return info.copy(scope = enrichScopeWithImports(info.scope, codeFragment))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getContextInfo(context: PsiElement?, resolutionFactory: (KtElement) -> BindingContext): ContextInfo {
|
||||||
var bindingContext: BindingContext = BindingContext.EMPTY
|
var bindingContext: BindingContext = BindingContext.EMPTY
|
||||||
var dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
|
var dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
|
||||||
var scope: LexicalScope? = null
|
var scope: LexicalScope? = null
|
||||||
@@ -99,7 +99,7 @@ class CodeFragmentAnalyzer(
|
|||||||
when (context) {
|
when (context) {
|
||||||
is KtPrimaryConstructor -> {
|
is KtPrimaryConstructor -> {
|
||||||
val containingClass = context.getContainingClassOrObject()
|
val containingClass = context.getContainingClassOrObject()
|
||||||
val resolutionResult = getClassDescriptor(containingClass, ::resolutionFactory)
|
val resolutionResult = getClassDescriptor(containingClass, resolutionFactory)
|
||||||
if (resolutionResult != null) {
|
if (resolutionResult != null) {
|
||||||
bindingContext = resolutionResult.bindingContext
|
bindingContext = resolutionResult.bindingContext
|
||||||
scope = resolutionResult.descriptor.scopeForInitializerResolution
|
scope = resolutionResult.descriptor.scopeForInitializerResolution
|
||||||
@@ -113,12 +113,22 @@ class CodeFragmentAnalyzer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is KtClassOrObject -> {
|
is KtClassOrObject -> {
|
||||||
val resolutionResult = getClassDescriptor(context, ::resolutionFactory)
|
val resolutionResult = getClassDescriptor(context, resolutionFactory)
|
||||||
if (resolutionResult != null) {
|
if (resolutionResult != null) {
|
||||||
bindingContext = resolutionResult.bindingContext
|
bindingContext = resolutionResult.bindingContext
|
||||||
scope = resolutionResult.descriptor.scopeForMemberDeclarationResolution
|
scope = resolutionResult.descriptor.scopeForMemberDeclarationResolution
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is KtFunction -> {
|
||||||
|
val bindingContextForFunction = resolutionFactory(context)
|
||||||
|
val functionDescriptor = bindingContextForFunction[BindingContext.FUNCTION, context]
|
||||||
|
if (functionDescriptor != null) {
|
||||||
|
bindingContext = bindingContextForFunction
|
||||||
|
val outerScope = getContextInfo(context.getParentOfType<KtDeclaration>(true), resolutionFactory).scope
|
||||||
|
val localRedeclarationChecker = LocalRedeclarationChecker.DO_NOTHING
|
||||||
|
scope = FunctionDescriptorUtil.getFunctionInnerScope(outerScope, functionDescriptor, localRedeclarationChecker)
|
||||||
|
}
|
||||||
|
}
|
||||||
is KtFile -> {
|
is KtFile -> {
|
||||||
bindingContext = resolveSession.bindingContext
|
bindingContext = resolveSession.bindingContext
|
||||||
scope = resolveSession.fileScopeProvider.getFileResolutionScope(context)
|
scope = resolveSession.fileScopeProvider.getFileResolutionScope(context)
|
||||||
@@ -131,15 +141,14 @@ class CodeFragmentAnalyzer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (scope == null) {
|
if (scope == null) {
|
||||||
val containingKtFile = codeFragment.context?.containingFile as? KtFile
|
val containingKtFile = context?.containingFile as? KtFile
|
||||||
if (containingKtFile != null) {
|
if (containingKtFile != null) {
|
||||||
bindingContext = resolveSession.bindingContext
|
bindingContext = resolveSession.bindingContext
|
||||||
scope = resolveSession.fileScopeProvider.getFileResolutionScope(containingKtFile)
|
scope = resolveSession.fileScopeProvider.getFileResolutionScope(containingKtFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val scopeWithImports = enrichScopeWithImports(scope ?: createEmptyScope(resolveSession.moduleDescriptor), codeFragment)
|
return ContextInfo(bindingContext, scope ?: createEmptyScope(resolveSession.moduleDescriptor), dataFlowInfo)
|
||||||
return ContextAnalysisResult(bindingContext, scopeWithImports, dataFlowInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class ClassResolutionResult(val bindingContext: BindingContext, val descriptor: ClassDescriptorWithResolutionScopes)
|
private data class ClassResolutionResult(val bindingContext: BindingContext, val descriptor: ClassDescriptorWithResolutionScopes)
|
||||||
@@ -164,7 +173,7 @@ class CodeFragmentAnalyzer(
|
|||||||
|
|
||||||
private fun refineContextElement(context: PsiElement?): KtElement? {
|
private fun refineContextElement(context: PsiElement?): KtElement? {
|
||||||
return when (context) {
|
return when (context) {
|
||||||
is KtParameter -> context.getParentOfType<KtFunction>(true)
|
is KtParameter -> context.getParentOfType<KtFunction>(true)?.let { it }
|
||||||
is KtProperty -> context.delegateExpressionOrInitializer
|
is KtProperty -> context.delegateExpressionOrInitializer
|
||||||
is KtConstructor<*> -> context
|
is KtConstructor<*> -> context
|
||||||
is KtFunctionLiteral -> context.bodyExpression?.statements?.lastOrNull()
|
is KtFunctionLiteral -> context.bodyExpression?.statements?.lastOrNull()
|
||||||
|
|||||||
+13
@@ -61,6 +61,8 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
|
|||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
|
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
|
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
@@ -70,6 +72,7 @@ import org.jetbrains.kotlin.psi.*
|
|||||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.org.objectweb.asm.*
|
import org.jetbrains.org.objectweb.asm.*
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
@@ -343,10 +346,18 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
if (result == null) {
|
if (result == null) {
|
||||||
val name = parameter.debugString
|
val name = parameter.debugString
|
||||||
|
|
||||||
|
fun isInsideDefaultInterfaceMethod(): Boolean {
|
||||||
|
val method = evaluationContext.frameProxy?.safeLocation()?.safeMethod() ?: return false
|
||||||
|
val desc = method.signature()
|
||||||
|
return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") }
|
||||||
|
}
|
||||||
|
|
||||||
if (parameter in compiledData.crossingBounds) {
|
if (parameter in compiledData.crossingBounds) {
|
||||||
evaluationException("'$name' is not captured")
|
evaluationException("'$name' is not captured")
|
||||||
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
|
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
|
||||||
evaluationException("Cannot find the backing field '${parameter.name}'")
|
evaluationException("Cannot find the backing field '${parameter.name}'")
|
||||||
|
} else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) {
|
||||||
|
evaluationException("Parameter evaluation is not supported for '\$default' methods")
|
||||||
} else {
|
} else {
|
||||||
throw VariableFinder.variableNotFound(evaluationContext, buildString {
|
throw VariableFinder.variableNotFound(evaluationContext, buildString {
|
||||||
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
|
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
|
||||||
@@ -363,6 +374,8 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
companion object {
|
companion object {
|
||||||
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS
|
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS
|
||||||
|
|
||||||
|
private val DEFAULT_METHOD_MARKERS = listOf(AsmTypes.OBJECT_TYPE, AsmTypes.DEFAULT_CONSTRUCTOR_MARKER)
|
||||||
|
|
||||||
private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? {
|
private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? {
|
||||||
val jdiValue = when (this) {
|
val jdiValue = when (this) {
|
||||||
is ValueReturned -> result
|
is ValueReturned -> result
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package defaultParameterValues
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(
|
||||||
|
b: Int,
|
||||||
|
//Breakpoint!
|
||||||
|
a: Int = 5
|
||||||
|
) {
|
||||||
|
val c = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: a
|
||||||
|
// RESULT: Parameter evaluation is not supported for '$default' methods
|
||||||
|
|
||||||
|
// EXPRESSION: b
|
||||||
|
// RESULT: Parameter evaluation is not supported for '$default' methods
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
LineBreakpoint created at defaultParameterValues.kt:10
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
defaultParameterValues.kt:10
|
||||||
|
Compile bytecode for a
|
||||||
|
Compile bytecode for b
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package defaultParameterValues2
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
Foo().foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFoo {
|
||||||
|
//Breakpoint!
|
||||||
|
fun foo(a: Int = 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo : IFoo {
|
||||||
|
override fun foo(a: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: Foo()
|
||||||
|
// RESULT: instance of defaultParameterValues2.Foo(id=ID): LdefaultParameterValues2/Foo;
|
||||||
|
|
||||||
|
// EXPRESSION: a
|
||||||
|
// RESULT: Parameter evaluation is not supported for '$default' methods
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
LineBreakpoint created at defaultParameterValues2.kt:9
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
defaultParameterValues2.kt:9
|
||||||
|
Compile bytecode for Foo()
|
||||||
|
Compile bytecode for a
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
Generated
+10
@@ -91,6 +91,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/collections.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/collections.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("defaultParameterValues.kt")
|
||||||
|
public void testDefaultParameterValues() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("defaultParameterValues2.kt")
|
||||||
|
public void testDefaultParameterValues2() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("delegatedPropertyInOtherFile.kt")
|
@TestMetadata("delegatedPropertyInOtherFile.kt")
|
||||||
public void testDelegatedPropertyInOtherFile() throws Exception {
|
public void testDelegatedPropertyInOtherFile() throws Exception {
|
||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedPropertyInOtherFile.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedPropertyInOtherFile.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user