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)
|
||||
}
|
||||
|
||||
private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextAnalysisResult: ContextAnalysisResult): BindingTrace {
|
||||
val (bindingContext, scope, dataFlowInfo) = contextAnalysisResult
|
||||
private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextInfo: ContextInfo): BindingTrace {
|
||||
val (bindingContext, scope, dataFlowInfo) = contextInfo
|
||||
val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis")
|
||||
|
||||
when (val contentElement = codeFragment.getContentElement()) {
|
||||
@@ -79,19 +79,19 @@ class CodeFragmentAnalyzer(
|
||||
return bindingTrace
|
||||
}
|
||||
|
||||
private data class ContextAnalysisResult(
|
||||
val bindingContext: BindingContext,
|
||||
val scope: LexicalScope,
|
||||
val dataFlowInfo: DataFlowInfo
|
||||
)
|
||||
private data class ContextInfo(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 {
|
||||
return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode)
|
||||
}
|
||||
|
||||
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 dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
|
||||
var scope: LexicalScope? = null
|
||||
@@ -99,7 +99,7 @@ class CodeFragmentAnalyzer(
|
||||
when (context) {
|
||||
is KtPrimaryConstructor -> {
|
||||
val containingClass = context.getContainingClassOrObject()
|
||||
val resolutionResult = getClassDescriptor(containingClass, ::resolutionFactory)
|
||||
val resolutionResult = getClassDescriptor(containingClass, resolutionFactory)
|
||||
if (resolutionResult != null) {
|
||||
bindingContext = resolutionResult.bindingContext
|
||||
scope = resolutionResult.descriptor.scopeForInitializerResolution
|
||||
@@ -113,12 +113,22 @@ class CodeFragmentAnalyzer(
|
||||
}
|
||||
}
|
||||
is KtClassOrObject -> {
|
||||
val resolutionResult = getClassDescriptor(context, ::resolutionFactory)
|
||||
val resolutionResult = getClassDescriptor(context, resolutionFactory)
|
||||
if (resolutionResult != null) {
|
||||
bindingContext = resolutionResult.bindingContext
|
||||
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 -> {
|
||||
bindingContext = resolveSession.bindingContext
|
||||
scope = resolveSession.fileScopeProvider.getFileResolutionScope(context)
|
||||
@@ -131,15 +141,14 @@ class CodeFragmentAnalyzer(
|
||||
}
|
||||
|
||||
if (scope == null) {
|
||||
val containingKtFile = codeFragment.context?.containingFile as? KtFile
|
||||
val containingKtFile = context?.containingFile as? KtFile
|
||||
if (containingKtFile != null) {
|
||||
bindingContext = resolveSession.bindingContext
|
||||
scope = resolveSession.fileScopeProvider.getFileResolutionScope(containingKtFile)
|
||||
}
|
||||
}
|
||||
|
||||
val scopeWithImports = enrichScopeWithImports(scope ?: createEmptyScope(resolveSession.moduleDescriptor), codeFragment)
|
||||
return ContextAnalysisResult(bindingContext, scopeWithImports, dataFlowInfo)
|
||||
return ContextInfo(bindingContext, scope ?: createEmptyScope(resolveSession.moduleDescriptor), dataFlowInfo)
|
||||
}
|
||||
|
||||
private data class ClassResolutionResult(val bindingContext: BindingContext, val descriptor: ClassDescriptorWithResolutionScopes)
|
||||
@@ -164,7 +173,7 @@ class CodeFragmentAnalyzer(
|
||||
|
||||
private fun refineContextElement(context: PsiElement?): KtElement? {
|
||||
return when (context) {
|
||||
is KtParameter -> context.getParentOfType<KtFunction>(true)
|
||||
is KtParameter -> context.getParentOfType<KtFunction>(true)?.let { it }
|
||||
is KtProperty -> context.delegateExpressionOrInitializer
|
||||
is KtConstructor<*> -> context
|
||||
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.variables.EvaluatorValueConverter
|
||||
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.util.application.executeWriteCommand
|
||||
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.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
@@ -343,10 +346,18 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
if (result == null) {
|
||||
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) {
|
||||
evaluationException("'$name' is not captured")
|
||||
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
|
||||
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 {
|
||||
throw VariableFinder.variableNotFound(evaluationContext, buildString {
|
||||
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 {
|
||||
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? {
|
||||
val jdiValue = when (this) {
|
||||
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");
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testDelegatedPropertyInOtherFile() throws Exception {
|
||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedPropertyInOtherFile.kt");
|
||||
|
||||
Reference in New Issue
Block a user