Evaluate Expression: support anonymous objects evaluation

This commit is contained in:
Natalia Ukhorskaya
2015-02-11 15:45:55 +03:00
parent f507af15c0
commit 0391fbd764
23 changed files with 355 additions and 129 deletions
@@ -17,8 +17,10 @@
package org.jetbrains.kotlin.psi.codeFragmentUtil
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetCodeFragment
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetTypeReference
import org.jetbrains.kotlin.types.JetType
public val SUPPRESS_DIAGNOSTICS_IN_DEBUG_MODE: Key<Boolean> = Key.create<Boolean>("SUPPRESS_DIAGNOSTICS_IN_DEBUG_MODE")
@@ -30,4 +32,15 @@ public var JetFile.suppressDiagnosticsInDebugMode: Boolean
}
set(skip: Boolean) {
putUserData(SUPPRESS_DIAGNOSTICS_IN_DEBUG_MODE, skip)
}
}
public val DEBUG_TYPE_REFERENCE_STRING: String = "DebugTypeKotlinRulezzzz"
public val DEBUG_TYPE_INFO: Key<JetType> = Key.create<JetType>("DEBUG_TYPE_INFO")
public var JetTypeReference.debugTypeInfo: JetType?
get() = getUserData(DEBUG_TYPE_INFO)
set(type: JetType?) {
if (type != null && this.getText() == DEBUG_TYPE_REFERENCE_STRING) {
putUserData(DEBUG_TYPE_INFO, type)
}
}
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.resolve.lazy.LazyEntity
import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.psi.debugText.getDebugText
import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo
public class TypeResolver(
private val annotationResolver: AnnotationResolver,
@@ -67,6 +68,12 @@ public class TypeResolver(
val cachedType = c.trace.getBindingContext().get(BindingContext.TYPE, typeReference)
if (cachedType != null) return type(cachedType)
if (typeReference.debugTypeInfo != null) {
val debugType = typeReference.debugTypeInfo
c.trace.record(BindingContext.TYPE, typeReference, debugType)
return type(debugType)
}
if (!c.allowBareTypes && lazinessToken.isLazy()) {
// Bare types can be allowed only inside expressions; lazy type resolution is only relevant for declarations
class LazyKotlinType : DelegatingType(), LazyEntity {
@@ -16,69 +16,63 @@
package org.jetbrains.kotlin.idea.debugger.evaluate
import org.jetbrains.kotlin.idea.debugger.evaluate.*
import com.intellij.psi.PsiElement
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.evaluation.*
import com.intellij.debugger.engine.SuspendContext
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.evaluation.expression.*
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.psi.JetFile
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.PsiFileFactory
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.CharsetToolkit
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
import org.jetbrains.org.objectweb.asm.*
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.LightVirtualFile
import com.sun.jdi.*
import com.sun.jdi.request.EventRequest
import org.jetbrains.eval4j.*
import org.jetbrains.eval4j.Value
import org.jetbrains.eval4j.jdi.JDIEval
import org.jetbrains.eval4j.jdi.asJdiValue
import org.jetbrains.eval4j.jdi.makeInitialFrame
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.codegen.ClassFileFactory
import org.jetbrains.eval4j.jdi.makeInitialFrame
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.psi.JetCodeFragment
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import com.sun.jdi.request.EventRequest
import com.sun.jdi.ObjectReference
import com.intellij.debugger.engine.SuspendContext
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluateExpressionCache.*
import org.jetbrains.kotlin.resolve.BindingContext
import com.sun.jdi.VirtualMachine
import org.jetbrains.kotlin.codegen.AsmUtil
import com.sun.jdi.InvalidStackFrameException
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.analysisContext
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.idea.caches.resolve.JavaResolveExtension
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.types.Flexibility
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
import com.intellij.openapi.diagnostic.Attachment
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
import com.sun.jdi.ClassType
import com.sun.jdi.InvocationException
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluateExpressionCache.CompiledDataDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluateExpressionCache.ParametersDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasses
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.types.Flexibility
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.MethodNode
private val RECEIVER_NAME = "\$receiver"
private val THIS_NAME = "this"
@@ -126,7 +120,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
val compiledData = KotlinEvaluateExpressionCache.getOrCreateCompiledData(codeFragment, sourcePosition, context) {
fragment, position ->
isCompiledDataFromCache = false
extractAndCompile(fragment, position)
extractAndCompile(fragment, position, context)
}
val result = runEval4j(context, compiledData)
@@ -134,7 +128,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition)).toJdiValue(virtualMachine)
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(virtualMachine)
}
return result.toJdiValue(virtualMachine)
@@ -159,7 +153,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
}
class object {
private fun extractAndCompile(codeFragment: JetCodeFragment, sourcePosition: SourcePosition): CompiledDataDescriptor {
private fun extractAndCompile(codeFragment: JetCodeFragment, sourcePosition: SourcePosition, context: EvaluationContextImpl): CompiledDataDescriptor {
codeFragment.checkForErrors()
val extractedFunction = getFunctionForExtractedFragment(codeFragment, sourcePosition.getFile(), sourcePosition.getLine())
@@ -167,7 +161,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
throw IllegalStateException("Code fragment cannot be extracted to function")
}
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction)
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context)
// KT-4509
val outputFiles = (classFileFactory : OutputFileCollection).asList()
@@ -273,7 +267,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
return parameterNames.zip(parameterTypes).map { this.findLocalVariable(it.first, it.second, checkType = false, failIfNotFound = true)!! }
}
private fun createClassFileFactory(codeFragment: JetCodeFragment, extractedFunction: JetNamedFunction): ClassFileFactory {
private fun createClassFileFactory(codeFragment: JetCodeFragment, extractedFunction: JetNamedFunction, context: EvaluationContextImpl): ClassFileFactory {
return runReadAction {
val file = createFileForDebugger(codeFragment, extractedFunction)
@@ -287,12 +281,44 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
listOf(file)
)
extractedFunction.getReceiverTypeReference()?.let {
state.recordAnonymousType(it, THIS_NAME, context)
}
for (param in extractedFunction.getValueParameters()) {
val paramRef = param.getTypeReference()
val paramName = param.getName()
if (paramRef == null || paramName == null) {
logger.error("Each parameter for extracted function should have a name and a type reference",
Attachment("codeFragment.txt", codeFragment.getText()),
Attachment("extractedFunction.txt", extractedFunction.getText()))
exception("An exception occurs during Evaluate Expression Action")
}
state.recordAnonymousType(paramRef, paramName, context)
}
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
state.getFactory()
}
}
private fun GenerationState.recordAnonymousType(typeReference: JetTypeReference, localVariableName: String, context: EvaluationContextImpl) {
val paramAnonymousType = typeReference.debugTypeInfo
if (paramAnonymousType != null) {
val declarationDescriptor = paramAnonymousType.getConstructor().getDeclarationDescriptor()
if (declarationDescriptor is ClassDescriptor) {
val localVariable = context.findLocalVariable(localVariableName, asmType = null, checkType = false, failIfNotFound = false)
if (localVariable == null) {
exception("Couldn't find local variable this in current frame to get classType for anonymous type ${paramAnonymousType}}")
}
getBindingTrace().record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType)
}
}
}
private fun exception(msg: String) = throw EvaluateExceptionUtil.createEvaluateException(msg)
private fun exception(e: Throwable): Nothing {
@@ -351,6 +377,18 @@ private fun createFileForDebugger(codeFragment: JetCodeFragment,
val jetFile = codeFragment.createJetFile("debugFile.kt", fileText)
jetFile.suppressDiagnosticsInDebugMode = true
val list = jetFile.getDeclarations()
val function = list.get(0) as JetNamedFunction
function.getReceiverTypeReference()?.debugTypeInfo = extractedFunction.getReceiverTypeReference()?.debugTypeInfo
for ((newParam, oldParam) in function.getValueParameters().zip(extractedFunction.getValueParameters())) {
newParam.getTypeReference()?.debugTypeInfo = oldParam.getTypeReference()?.debugTypeInfo
}
function.getTypeReference()?.debugTypeInfo = extractedFunction.getTypeReference()?.debugTypeInfo
return jetFile
}
@@ -451,8 +489,8 @@ fun EvaluationContextImpl.findLocalVariable(name: String, asmType: Type?, checkT
}
}
fun Value.isSharedVar(expectedType: Type?): Boolean {
return expectedType != null && this.asmType == StackValue.sharedTypeForType(expectedType)
fun Value.isSharedVar(): Boolean {
return this.asmType.getSort() == Type.OBJECT && this.asmType.getInternalName().startsWith(AsmTypes.REF_TYPE_PREFIX)
}
fun getValueForSharedVar(value: Value, expectedType: Type?, checkType: Boolean): Value? {
@@ -466,7 +504,7 @@ fun EvaluationContextImpl.findLocalVariable(name: String, asmType: Type?, checkT
val localVariable = frame.visibleVariableByName(name)
if (localVariable != null) {
val eval4jValue = frame.getValue(localVariable).asValue()
if (eval4jValue.isSharedVar(asmType)) {
if (eval4jValue.isSharedVar()) {
val sharedVarValue = getValueForSharedVar(eval4jValue, asmType, checkType)
if (sharedVarValue != null) {
return sharedVarValue
@@ -493,7 +531,7 @@ fun EvaluationContextImpl.findLocalVariable(name: String, asmType: Type?, checkT
val capturedValName = getCapturedFieldName(name)
val capturedVal = findCapturedVal(capturedValName)
if (capturedVal != null) {
if (capturedVal.isSharedVar(asmType)) {
if (capturedVal.isSharedVar()) {
val sharedVarValue = getValueForSharedVar(capturedVal, asmType, checkType)
if (sharedVarValue != null) {
return sharedVarValue
@@ -95,7 +95,7 @@ fun getFunctionForExtractedFragment(
if (targetSibling == null) return null
val analysisResult = ExtractionData(
tmpFile, newDebugExpression.toRange(), targetSibling, ExtractionOptions(false, true)
tmpFile, newDebugExpression.toRange(), targetSibling, ExtractionOptions(false, true, true)
).performAnalysis()
if (analysisResult.status != Status.SUCCESS) {
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult))
@@ -63,10 +63,12 @@ trait Parameter {
val originalDescriptor: DeclarationDescriptor
val name: String
val mirrorVarName: String?
val parameterType: JetType
val parameterTypeCandidates: List<JetType>
val receiverCandidate: Boolean
fun getParameterType(allowSpecialClassNames: Boolean): JetType
fun getParameterTypeCandidates(allowSpecialClassNames: Boolean): List<JetType>
fun copy(name: String, parameterType: JetType): Parameter
}
@@ -143,7 +145,7 @@ trait OutputValue {
val parameter: Parameter,
override val originalExpressions: List<JetExpression>
): OutputValue {
override val valueType: JetType get() = parameter.parameterType
override val valueType: JetType get() = parameter.getParameterType(false)
}
class Initializer(
@@ -55,10 +55,11 @@ import org.jetbrains.kotlin.idea.refactoring.compareDescriptors
data class ExtractionOptions(
val inferUnitTypeForUnusedValues: Boolean,
val enableListBoxing: Boolean
val enableListBoxing: Boolean,
val allowSpecialClassNames: Boolean
) {
class object {
val DEFAULT = ExtractionOptions(true, false)
val DEFAULT = ExtractionOptions(true, false, false)
}
}
@@ -441,6 +441,7 @@ private fun JetType.isExtractable(): Boolean {
private fun JetType.processTypeIfExtractable(
typeParameters: MutableSet<TypeParameter>,
nonDenotableTypes: MutableSet<JetType>,
options: ExtractionOptions,
processTypeArguments: Boolean = true
): Boolean {
return collectReferencedTypes(processTypeArguments).fold(true) { (extractable, typeToCheck) ->
@@ -458,6 +459,9 @@ private fun JetType.processTypeIfExtractable(
typeToCheck.canBeReferencedViaImport() ->
extractable
options.allowSpecialClassNames && typeToCheck.isSpecial() ->
extractable
typeToCheck.isError() ->
false
@@ -501,7 +505,7 @@ private class MutableParameter(
CommonSupertypes.commonSupertype(defaultTypes)
}
override val parameterTypeCandidates: List<JetType> by Delegates.lazy {
private val parameterTypeCandidates: List<JetType> by Delegates.lazy {
writable = false
val typePredicate = and(typePredicates)
@@ -522,10 +526,20 @@ private class MutableParameter(
typeList.add(superType)
}
typeList.filter { it.isExtractable() }
typeList
}
override val parameterType: JetType by Delegates.lazy { parameterTypeCandidates.firstOrNull() ?: defaultType }
override fun getParameterTypeCandidates(allowSpecialClassNames: Boolean): List<JetType> {
return if (!allowSpecialClassNames) {
parameterTypeCandidates.filter { it.isExtractable() }
} else {
parameterTypeCandidates
}
}
override fun getParameterType(allowSpecialClassNames: Boolean): JetType {
return getParameterTypeCandidates(allowSpecialClassNames).firstOrNull() ?: defaultType
}
override fun copy(name: String, parameterType: JetType): Parameter = DelegatingParameter(this, name, parameterType)
}
@@ -533,9 +547,10 @@ private class MutableParameter(
private class DelegatingParameter(
val original: Parameter,
override val name: String,
override val parameterType: JetType
val parameterType: JetType
): Parameter by original {
override fun copy(name: String, parameterType: JetType): Parameter = DelegatingParameter(original, name, parameterType)
override fun getParameterType(allowSpecialClassNames: Boolean) = parameterType
}
private class ParametersInfo {
@@ -596,7 +611,7 @@ private fun ExtractionData.inferParametersInfo(
if (referencedClassDescriptor != null) {
if (!referencedClassDescriptor.getDefaultType().processTypeIfExtractable(
info.typeParameters, info.nonDenotableTypes, false
info.typeParameters, info.nonDenotableTypes, options, false
)) continue
info.replacementMap[refInfo.offsetInBody] = FqNameReplacement(originalDescriptor.importableFqNameSafe)
@@ -657,11 +672,11 @@ private fun ExtractionData.inferParametersInfo(
)
for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) {
if (!parameter.parameterType.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes)) continue
if (!parameter.getParameterType(options.allowSpecialClassNames).processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options)) continue
with (parameter) {
if (currentName == null) {
currentName = JetNameSuggester.suggestNames(parameterType, varNameValidator, "p").first()
currentName = JetNameSuggester.suggestNames(getParameterType(options.allowSpecialClassNames), varNameValidator, "p").first()
}
mirrorVarName = if (descriptorToExtract in modifiedVarDescriptors) varNameValidator.validateName(name) else null
info.parameters.add(this)
@@ -669,7 +684,7 @@ private fun ExtractionData.inferParametersInfo(
}
for (typeToCheck in info.typeParameters.flatMapTo(HashSet<JetType>()) { it.collectReferencedTypes(bindingContext) }) {
typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes)
typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options)
}
@@ -763,7 +778,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
controlFlowMessage?.let { messages.add(it) }
val returnType = controlFlow.outputValueBoxer.returnType
returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes)
returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options)
if (paramsInfo.nonDenotableTypes.isNotEmpty()) {
val typeStr = paramsInfo.nonDenotableTypes.map {it.renderForMessage()}.sort()
@@ -16,49 +16,39 @@
package org.jetbrains.kotlin.idea.refactoring.extractFunction
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import java.util.HashMap
import org.jetbrains.kotlin.psi.JetTreeVisitorVoid
import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.psi.JetExpression
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.JetDeclaration
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder
import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder.Target
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.JetPsiFactory
import java.util.LinkedHashMap
import java.util.Collections
import org.jetbrains.kotlin.psi.psiUtil.isFunctionLiteralOutsideParentheses
import org.jetbrains.kotlin.psi.JetFunctionLiteralArgument
import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.psi.psiUtil.appendElement
import org.jetbrains.kotlin.psi.psiUtil.replaced
import org.jetbrains.kotlin.psi.JetBlockExpression
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.ParameterUpdate
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.Jump
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.Initializer
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.ExpressionValue
import org.jetbrains.kotlin.psi.JetReturnExpression
import org.jetbrains.kotlin.idea.refactoring.JetNameValidatorImpl
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.refactoring.JetNameSuggester
import org.jetbrains.kotlin.idea.refactoring.isMultiLine
import org.jetbrains.kotlin.idea.refactoring.JetNameValidatorImpl
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.ExpressionValue
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.Initializer
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.Jump
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValue.ParameterUpdate
import org.jetbrains.kotlin.idea.refactoring.extractFunction.OutputValueBoxer.AsTuple
import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnifierParameter
import org.jetbrains.kotlin.idea.refactoring.isMultiLine
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiRange
import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiRange.Match
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.WeaklyMatched
import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.StronglyMatched
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.WeaklyMatched
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnifierParameter
import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder
import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder.Target
import org.jetbrains.kotlin.psi.codeFragmentUtil.DEBUG_TYPE_REFERENCE_STRING
import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.TypeUtils
import java.util.ArrayList
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import java.util.Collections
import java.util.HashMap
import java.util.LinkedHashMap
fun ExtractableCodeDescriptor.getDeclarationText(
options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT,
@@ -77,16 +67,20 @@ fun ExtractableCodeDescriptor.getDeclarationText(
builder.typeParams(typeParameters.map { it.originalDeclaration.getText()!! })
receiverParameter?.let { builder.receiver(descriptorRenderer.renderType(it.parameterType)) }
fun JetType.typeAsString(): String {
return if (isSpecial()) DEBUG_TYPE_REFERENCE_STRING else descriptorRenderer.renderType(this)
}
receiverParameter?.let { builder.receiver(it.getParameterType(extractionData.options.allowSpecialClassNames).typeAsString()) }
builder.name(if (name != "") name else DEFAULT_FUNCTION_NAME)
parameters.forEach { parameter ->
builder.param(parameter.name, descriptorRenderer.renderType(parameter.parameterType))
builder.param(parameter.name, parameter.getParameterType(extractionData.options.allowSpecialClassNames).typeAsString())
}
with(controlFlow.outputValueBoxer.returnType) {
if (isDefault() || isError()) builder.noReturnType() else builder.returnType(descriptorRenderer.renderType(this))
if (isDefault() || isError()) builder.noReturnType() else builder.returnType(this.typeAsString())
}
builder.typeConstraints(typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! })
@@ -99,6 +93,10 @@ fun ExtractableCodeDescriptor.getDeclarationText(
}
}
fun JetType.isSpecial(): Boolean {
return this.getConstructor().getDeclarationDescriptor()?.getName()?.isSpecial() ?: false
}
fun createNameCounterpartMap(from: JetElement, to: JetElement): Map<JetSimpleNameExpression, JetSimpleNameExpression> {
val map = HashMap<JetSimpleNameExpression, JetSimpleNameExpression>()
@@ -172,7 +170,7 @@ fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo> {
return if (matched) newControlFlow else null
}
val unifierParameters = parameters.map { UnifierParameter(it.originalDescriptor, it.parameterType) }
val unifierParameters = parameters.map { UnifierParameter(it.originalDescriptor, it.getParameterType(extractionData.options.allowSpecialClassNames)) }
val unifier = JetPsiUnifier(unifierParameters, true)
@@ -527,6 +525,18 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
val declaration = createDeclaration().let { if (options.inTempFile) it else insertDeclaration(it, anchor) }
adjustDeclarationBody(declaration)
if (declaration is JetNamedFunction && declaration.getContainingJetFile().suppressDiagnosticsInDebugMode) {
declaration.getReceiverTypeReference()?.debugTypeInfo = receiverParameter?.getParameterType(true)
for ((i, param) in declaration.getValueParameters().withIndex()) {
param.getTypeReference()?.debugTypeInfo = parameters[i].getParameterType(true)
}
if (declaration.getTypeReference() != null) {
declaration.getTypeReference()?.debugTypeInfo = KotlinBuiltIns.getInstance().getAnyType()
}
}
if (options.inTempFile) return ExtractionResult(declaration, Collections.emptyMap(), nameByOffset)
makeCall(this, declaration, controlFlow, extractionData.originalRange, parameters.map { it.argumentText })
@@ -55,7 +55,7 @@ public class KotlinParameterTablePanel extends JPanel {
public ParameterInfo(Parameter originalParameter) {
this.originalParameter = originalParameter;
this.name = originalParameter.getName();
this.type = originalParameter.getParameterType();
this.type = originalParameter.getParameterType(false);
}
public Parameter getOriginalParameter() {
@@ -176,7 +176,7 @@ public class KotlinParameterTablePanel extends JPanel {
ParameterInfo info = parameterInfos.get(row);
myEditorComponent.setCell(table, row, column);
myEditorComponent.setOptions(info.getOriginalParameter().getParameterTypeCandidates().toArray());
myEditorComponent.setOptions(info.getOriginalParameter().getParameterTypeCandidates(false).toArray());
myEditorComponent.setDefaultValue(info.getType());
myEditorComponent.setToString(new Function<Object, String>() {
@Override
@@ -360,7 +360,7 @@ public class KotlinParameterTablePanel extends JPanel {
case PARAMETER_NAME_COLUMN:
return isEnabled() && info.isEnabled();
case PARAMETER_TYPE_COLUMN:
return isEnabled() && info.isEnabled() && info.getOriginalParameter().getParameterTypeCandidates().size() > 1;
return isEnabled() && info.isEnabled() && info.getOriginalParameter().getParameterTypeCandidates(false).size() > 1;
default:
return false;
}
@@ -0,0 +1,13 @@
LineBreakpoint created at ceAnonymousObject.kt:18
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! ceAnonymousObject.CeAnonymousObjectPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceAnonymousObject.kt:18
Compile bytecode for publicTopLevelObject
Compile bytecode for privateTopLevelObject
Compile bytecode for publicObject
Compile bytecode for protectedObject
Compile bytecode for localObject
Compile bytecode for localObject.test()
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,9 @@
LineBreakpoint created at ceAnonymousObjectCapturedInClosure.kt:7
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! ceAnonymousObjectCapturedInClosure.CeAnonymousObjectCapturedInClosurePackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceAnonymousObjectCapturedInClosure.kt:7
Compile bytecode for lambda { localObject.test() }
Compile bytecode for lambda { localObjectVar.test() }
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,9 @@
LineBreakpoint created at ceAnonymousObjectThisAsReceiver.kt:9
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! ceAnonymousObjectThisAsReceiver.CeAnonymousObjectThisAsReceiverPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceAnonymousObjectThisAsReceiver.kt:9
Compile bytecode for test()
Compile bytecode for this.test()
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -2,6 +2,8 @@ LineBreakpoint created at ceObject.kt:5
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! ceObject.CeObjectPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceObject.kt:5
Compile bytecode for (object: T {}).test()
Compile bytecode for object: T {}
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,45 @@
package ceAnonymousObject
public val publicTopLevelObject: Any = object { fun test() = 1 }
private val privateTopLevelObject = object { fun test() = 1 }
fun main(args: Array<String>) {
MyClass().foo()
}
class MyClass {
public val publicObject: Any = object { fun test() = 1 }
protected val protectedObject: Any = object { fun test() = 1 }
private val privateObject = object { fun test() = 1 }
fun foo() {
val localObject = object { fun test() = 1 }
//Breakpoint!
val a = 1
}
}
// EXPRESSION: publicTopLevelObject
// RESULT: instance of ceAnonymousObject.CeAnonymousObjectPackage$ceAnonymousObject$@packagePartHASH$publicTopLevelObject$1(id=ID): LceAnonymousObject/CeAnonymousObjectPackage$ceAnonymousObject$@packagePartHASH$publicTopLevelObject$1;
// EXPRESSION: privateTopLevelObject
// RESULT: instance of ceAnonymousObject.CeAnonymousObjectPackage$ceAnonymousObject$@packagePartHASH$privateTopLevelObject$1(id=ID): LceAnonymousObject/CeAnonymousObjectPackage$ceAnonymousObject$@packagePartHASH$privateTopLevelObject$1;
// EXPRESSION: publicObject
// RESULT: instance of ceAnonymousObject.MyClass$publicObject$1(id=ID): LceAnonymousObject/MyClass$publicObject$1;
// EXPRESSION: protectedObject
// RESULT: instance of ceAnonymousObject.MyClass$protectedObject$1(id=ID): LceAnonymousObject/MyClass$protectedObject$1;
// -EXPRESSION: privateObject
// -RESULT: 1
// -EXPRESSION: privateObject.test()
// -RESULT: 1: I
// EXPRESSION: localObject
// RESULT: instance of ceAnonymousObject.MyClass$foo$localObject$1(id=ID): LceAnonymousObject/MyClass$foo$localObject$1;
// EXPRESSION: localObject.test()
// RESULT: 1: I
@@ -0,0 +1,16 @@
package ceAnonymousObjectCapturedInClosure
fun main(args: Array<String>) {
val localObject = object { fun test() = 1 }
var localObjectVar = object { fun test() = 1 }
//Breakpoint!
lambda { localObjectVar.test() }
}
fun lambda(f: () -> Int) = f()
// EXPRESSION: lambda { localObject.test() }
// RESULT: 1: I
// EXPRESSION: lambda { localObjectVar.test() }
// RESULT: 1: I
@@ -0,0 +1,20 @@
package ceAnonymousObjectThisAsReceiver
fun main(args : Array<String>) {
val localObject = object {
fun test() = 1
fun foo() {
//Breakpoint!
val a = 1
}
}
localObject.foo()
}
// EXPRESSION: test()
// RESULT: 1: I
// EXPRESSION: this.test()
// RESULT: 1: I
@@ -9,5 +9,8 @@ trait T {
fun test() = 1
}
//- EXPRESSION: (object: T {}).test()
//- RESULT: 1: I
// EXPRESSION: (object: T {}).test()
// RESULT: 1: I
// EXPRESSION: object: T {}
// RESULT: instance of packageForDebugger.PackageForDebuggerPackage$debugFile$@packagePartHASH$myFun$1(id=ID): LpackageForDebugger/PackageForDebuggerPackage$debugFile$@packagePartHASH$myFun$1;
@@ -1,3 +0,0 @@
object {}
// RESULT: Cannot perform an action because following types are unavailable from debugger scope: <no name provided>
@@ -1,5 +1,5 @@
// WITH_RUNTIME
// OPTIONS: true, true
// OPTIONS: true, true, false
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
@@ -1,5 +1,5 @@
// WITH_RUNTIME
// OPTIONS: true, true
// OPTIONS: true, true, false
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
@@ -63,6 +63,8 @@ import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.SourcePositionProvider
import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.ui.impl.watch.WatchItemDescriptor
import java.util.regex.Matcher
import java.util.regex.Pattern
public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
private val logger = Logger.getLogger(javaClass<KotlinEvaluateExpressionCache>())!!
@@ -353,8 +355,11 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestB
private fun Value.asString(): String {
if (this is ObjectValue && this.value is ObjectReference) {
return this.toString().replaceFirst("id=[0-9]*", "id=ID")
val regexMatcher = PACKAGE_PART_PATTERN.matcher(this.toString())
return regexMatcher.replaceAll("$1@packagePartHASH").replaceFirst("id=[0-9]*", "id=ID")
}
return this.toString()
}
}
private val PACKAGE_PART_PATTERN = Pattern.compile("(Package\\$[\\w]*\\$)([0-9a-f]+)");
@@ -243,6 +243,24 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ceAnonymousObject.kt")
public void testCeAnonymousObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceAnonymousObject.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceAnonymousObjectCapturedInClosure.kt")
public void testCeAnonymousObjectCapturedInClosure() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceAnonymousObjectCapturedInClosure.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceAnonymousObjectThisAsReceiver.kt")
public void testCeAnonymousObjectThisAsReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceAnonymousObjectThisAsReceiver.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceLambda.kt")
public void testCeLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceLambda.kt");
@@ -44,6 +44,7 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.JetPackageDirective
import org.jetbrains.kotlin.utils.emptyOrSingletonList
import kotlin.test.assertTrue
public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST
@@ -96,7 +97,9 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
if (it.isNotEmpty()) {
[suppress("CAST_NEVER_SUCCEEDS")]
val args = it.map { it.toBoolean() }.copyToArray() as Array<Any?>
javaClass<ExtractionOptions>().getConstructors()[0].newInstance(*args) as ExtractionOptions
val constructor = javaClass<ExtractionOptions>().getConstructors()[0]
assertTrue(constructor.getParameterTypes().size() == args.size(), "Wrong number of parameters was passed for ExtractOptions constructor: expected = ${constructor.getParameterTypes().size()}, actual = ${args.size()}. \nTest directive: // OPTIONS: $it")
constructor.newInstance(*args) as ExtractionOptions
} else ExtractionOptions.DEFAULT
}
@@ -118,7 +121,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
val actualTypes = allParameters.map {
it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]")
it.getParameterTypeCandidates(extractionOptions.allowSpecialClassNames).map { renderer.renderType(it) }.joinToString(", ", "[", "]")
}.joinToString()
assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.")