Evaluator: Handle function context gracefully. Use file scope as a fallback instead of error scope

This commit is contained in:
Yan Zhulanow
2019-02-21 00:31:10 +03:00
parent 5035de24ac
commit 16266259f5
7 changed files with 103 additions and 14 deletions
@@ -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()
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");