Evaluate expression: refactor error messages

This commit is contained in:
Natalia Ukhorskaya
2014-05-19 12:47:58 +04:00
parent 47ecfd0f8d
commit d263621ef5
11 changed files with 169 additions and 70 deletions
@@ -55,15 +55,18 @@ import com.intellij.psi.PsiFile
import org.jetbrains.jet.codegen.ClassFileFactory
import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils
import org.jetbrains.jet.OutputFileCollection
import org.jetbrains.jet.lang.psi.JetExpressionCodeFragment
import org.jetbrains.jet.plugin.caches.resolve.getAnalysisResults
import org.jetbrains.jet.lang.psi.JetCodeFragment
import org.jetbrains.jet.lang.psi.JetImportList
import org.jetbrains.jet.lang.psi.codeFragmentUtil.setSkipVisibilityCheck
import org.jetbrains.jet.lang.psi.JetBlockCodeFragment
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.jet.codegen.CompilationErrorHandler
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.ErrorMessage
import org.jetbrains.jet.lang.diagnostics.Severity
import org.jetbrains.jet.lang.diagnostics.rendering.DefaultErrorMessages
object KotlinEvaluationBuilder: EvaluatorBuilder {
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
@@ -91,14 +94,14 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
try {
val extractedFunction = getFunctionForExtractedFragment(codeFragment, sourcePosition.getFile(), sourcePosition.getLine())
if (extractedFunction == null) {
exception("This code fragment cannot be extracted to function")
throw IllegalStateException("Code fragment cannot be extracted to function: ${sourcePosition.getFile().getText()}:${sourcePosition.getLine()},\ncodeFragment = ${codeFragment.getText()}")
}
val classFileFactory = createClassFileFactory(extractedFunction)
// KT-4509
val outputFiles = (classFileFactory : OutputFileCollection).asList().filter { it.relativePath != "$packageInternalName.class" }
if (outputFiles.size() != 1) exception("More than one class file found. Note that lambdas, classes and objects are unsupported yet.\n${outputFiles.makeString("\n")}")
if (outputFiles.size() != 1) exception("Expression compiles to more than one class file. Note that lambdas, classes and objects are unsupported yet. List of files: ${outputFiles.makeString(",")}")
val virtualMachine = context.getDebugProcess().getVirtualMachineProxy().getVirtualMachine()
@@ -129,7 +132,9 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
}
}, 0)
if (resultValue == null) exception("Cannot evaluate expression")
if (resultValue == null) {
throw IllegalStateException("resultValue is null: cannot find method ${extractedFunction.getName()} in ${outputFiles.first().relativePath}")
}
return resultValue!!.asJdiValue(virtualMachine, resultValue!!.asmType)
}
@@ -161,21 +166,19 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
private fun EvaluationContextImpl.getArgumentsByNames(parameterNames: List<String>): List<Value> {
val frames = getFrameProxy()?.getStackFrame()
if (frames != null) {
try {
return parameterNames.map {
name ->
if (name == "this") {
frames.thisObject().asValue()
}
else {
frames.getValue(frames.visibleVariableByName(name)).asValue()
fun getValue(name: String): Value {
return try {
when (name) {
"this" -> frames.thisObject().asValue()
else -> frames.getValue(frames.visibleVariableByName(name)).asValue()
}
}
catch(e: Exception) {
exception("Cannot get parameter value from local variables table: parameterName = ${name}. Note that captured parameters are unsupported yet.")
}
}
catch(e: Throwable) {
throw IllegalArgumentException(
"Cannot get parameter values from VirtualMachine: ${e.javaClass}\nFunction parameters:\n${parameterNames.makeString("\n")}\nVisible variables:\n${frames.visibleVariables().makeString("\n")}")
}
return parameterNames.map { getValue(it) }
}
return Collections.emptyList()
}
@@ -188,13 +191,16 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
checkForSyntacticErrors(file)
val analyzeExhaust = file.getAnalysisResults()
val bindingContext = analyzeExhaust.getBindingContext()
try {
analyzeExhaust.throwIfError()
AnalyzingUtils.throwExceptionOnErrors(bindingContext)
if (analyzeExhaust.isError()) {
exception(analyzeExhaust.getError())
}
catch (e: IllegalStateException) {
exception(e.getMessage() ?: "Exception from kotlin compiler")
val bindingContext = analyzeExhaust.getBindingContext()
bindingContext.getDiagnostics().forEach {
diagnostic ->
if (diagnostic.getSeverity() == Severity.ERROR) {
exception(DefaultErrorMessages.RENDERER.render(diagnostic))
}
}
val state = GenerationState(
@@ -205,10 +211,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
listOf(file)
)
KotlinCodegenFacade.compileCorrectFiles(state) {
e, msg ->
exception("$msg\n${e?.getMessage() ?: ""}")
}
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
return state.getFactory()
}
@@ -217,6 +220,14 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
}
private fun exception(msg: String) = throw EvaluateExceptionUtil.createEvaluateException(msg)
private fun exception(e: Throwable) {
val message = e.getMessage()
if (message != null) {
exception(message)
}
throw EvaluateExceptionUtil.createEvaluateException(e)
}
}
private val template = """
@@ -295,6 +306,26 @@ private fun getFunctionForExtractedFragment(
breakpointFile: PsiFile,
breakpointLine: Int
): JetNamedFunction? {
fun getErrorMessageForExtractFunctionResult(analysisResult: AnalysisResult): String {
return analysisResult.messages.map {
errorMessage ->
val message = when(errorMessage) {
ErrorMessage.NO_EXPRESSION -> "Cannot perform an action without an expression"
ErrorMessage.NO_CONTAINER -> "Cannot perform an action at this breakpoint ${breakpointFile.getName()}:${breakpointLine}"
ErrorMessage.SUPER_CALL -> "Cannot perform an action for expression with super call"
ErrorMessage.DENOTABLE_TYPES -> "Cannot perform an action because following types are unavailable from debugger scope"
ErrorMessage.MULTIPLE_OUTPUT -> "Cannot perform an action because this code fragment changes more than one variable"
ErrorMessage.NON_LOCAL_DECLARATION,
ErrorMessage.DECLARATIONS_OUT_OF_SCOPE,
ErrorMessage.OUTPUT_AND_EXIT_POINT,
ErrorMessage.MULTIPLE_EXIT_POINTS,
ErrorMessage.VARIABLES_ARE_USED_OUTSIDE -> "Cannot perform an action for this expression"
}
if (errorMessage.additionalInfo == null) message else "$message: ${errorMessage.additionalInfo?.makeString(", ")}"
}.makeString(", ")
}
return ApplicationManager.getApplication()?.runReadAction(object: Computable<JetNamedFunction> {
override fun compute(): JetNamedFunction? {
checkForSyntacticErrors(codeFragment)
@@ -322,12 +353,12 @@ private fun getFunctionForExtractedFragment(
val analysisResult = ExtractionData(tmpFile, Collections.singletonList(newDebugExpression), nextSibling).performAnalysis()
if (analysisResult.status != Status.SUCCESS) {
throw EvaluateExceptionUtil.createEvaluateException(analysisResult.messages.makeString(", "))
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult))
}
val validationResult = analysisResult.descriptor!!.validate()
if (!validationResult.conflicts.isEmpty()) {
throw EvaluateExceptionUtil.createEvaluateException("Some declarations are unavailable: ${validationResult.conflicts.keySet()?.map { it.getText() }?.makeString(",")}")
throw EvaluateExceptionUtil.createEvaluateException("Following declarations are unavailable in debug scope: ${validationResult.conflicts.keySet()?.map { it.getText() }?.makeString(",")}")
}
return validationResult.descriptor.generateFunction(true)
@@ -63,7 +63,7 @@ public class ExtractKotlinFunctionHandler : RefactoringActionHandler {
val analysisResult = ExtractionData(file, elements, nextSibling).performAnalysis()
if (ApplicationManager.getApplication()!!.isUnitTestMode() && analysisResult.status != Status.SUCCESS) {
throw ConflictsInTestsException(analysisResult.messages)
throw ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() })
}
fun proceedWithExtraction() {
@@ -84,7 +84,7 @@ public class ExtractKotlinFunctionHandler : RefactoringActionHandler {
project.executeWriteCommand(EXTRACT_FUNCTION) { descriptor.generateFunction() }
}
val message = analysisResult.messages.makeString("\n")
val message = analysisResult.messages.map { it.renderMessage() }.makeString("\n")
when (analysisResult.status) {
Status.CRITICAL_ERROR -> {
showErrorHint(project, editor, message)
@@ -33,6 +33,8 @@ import org.jetbrains.jet.lang.psi.JetTypeParameter
import org.jetbrains.jet.lang.psi.JetTypeConstraint
import kotlin.properties.Delegates
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.ErrorMessage
data class Parameter(
val argumentText: String,
@@ -137,13 +139,52 @@ data class ExtractionDescriptor(
class AnalysisResult (
val descriptor: ExtractionDescriptor?,
val status: Status,
val messages: List<String>
val messages: List<ErrorMessage>
) {
enum class Status {
SUCCESS
NON_CRITICAL_ERROR
CRITICAL_ERROR
}
enum class ErrorMessage {
NO_EXPRESSION
NO_CONTAINER
SUPER_CALL
NON_LOCAL_DECLARATION
DENOTABLE_TYPES
MULTIPLE_OUTPUT
OUTPUT_AND_EXIT_POINT
MULTIPLE_EXIT_POINTS
VARIABLES_ARE_USED_OUTSIDE
DECLARATIONS_OUT_OF_SCOPE
var additionalInfo: List<String>? = null
fun addAdditionalInfo(info: List<String>): ErrorMessage {
additionalInfo = info
return this
}
fun renderMessage(): String {
val message = JetRefactoringBundle.message(when(this) {
NO_EXPRESSION -> "cannot.refactor.no.expresson"
NO_CONTAINER -> "cannot.refactor.no.container"
SUPER_CALL -> "cannot.extract.super.call"
NON_LOCAL_DECLARATION -> "cannot.extract.non.local.declaration.ref"
DENOTABLE_TYPES -> "parameter.types.are.not.denotable"
MULTIPLE_OUTPUT -> "selected.code.fragment.has.multiple.output.values"
OUTPUT_AND_EXIT_POINT -> "selected.code.fragment.has.output.values.and.exit.points"
MULTIPLE_EXIT_POINTS -> "selected.code.fragment.has.multiple.exit.points"
VARIABLES_ARE_USED_OUTSIDE -> "variables.are.used.outside.of.selected.code.fragment"
DECLARATIONS_OUT_OF_SCOPE -> "declarations.will.move.out.of.scope"
})!!
if (additionalInfo != null) {
return "$message\n${additionalInfo?.makeString("\n")}"
}
return message
}
}
}
class ExtractionDescriptorWithConflicts(
@@ -100,6 +100,7 @@ import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Statu
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import org.jetbrains.jet.lang.psi.codeFragmentUtil.skipVisibilityCheck
import org.jetbrains.jet.lang.psi.codeFragmentUtil.setSkipVisibilityCheck
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.ErrorMessage
private val DEFAULT_FUNCTION_NAME = "myFun"
private val DEFAULT_RETURN_TYPE = KotlinBuiltIns.getInstance().getUnitType()
@@ -146,15 +147,14 @@ private fun List<Instruction>.analyzeControlFlow(
bindingContext: BindingContext,
parameters: Set<Parameter>,
inferredType: JetType?
): Pair<ControlFlow, String?> {
): Pair<ControlFlow, ErrorMessage?> {
val outParameters = parameters.filterTo(HashSet<Parameter>()) { it.mirrorVarName != null }
if (outParameters.size > 1) {
val outValuesStr = outParameters.map { it.argumentText }.sort().makeString(separator = "\n")
val outValuesStr = outParameters.map { it.argumentText }.sort()
return Pair(
DefaultControlFlow,
"${JetRefactoringBundle.message("selected.code.fragment.has.multiple.output.values")!!}\n$outValuesStr"
ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr)
)
}
@@ -190,7 +190,7 @@ private fun List<Instruction>.analyzeControlFlow(
if (outParameters.isNotEmpty()) {
if (hasMeaningfulType || valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) {
return Pair(DefaultControlFlow, JetRefactoringBundle.message("selected.code.fragment.has.output.values.and.exit.points")!!)
return Pair(DefaultControlFlow, ErrorMessage.OUTPUT_AND_EXIT_POINT)
}
return Pair(ParameterUpdate(outParameters.first()), null)
@@ -198,7 +198,7 @@ private fun List<Instruction>.analyzeControlFlow(
val multipleExitsError = Pair(
DefaultControlFlow,
JetRefactoringBundle.message("selected.code.fragment.has.multiple.exit.points")!!
ErrorMessage.MULTIPLE_EXIT_POINTS
)
if (hasMeaningfulType) {
@@ -310,7 +310,7 @@ private fun ExtractionData.inferParametersInfo(
replacementMap: MutableMap<Int, Replacement>,
parameters: MutableSet<Parameter>,
typeParameters: MutableSet<TypeParameter>
): String? {
): ErrorMessage? {
val varNameValidator = JetNameValidatorImpl(
commonParent.getParentByType(javaClass<JetExpression>()),
originalElements.first,
@@ -328,7 +328,7 @@ private fun ExtractionData.inferParametersInfo(
val selector = (ref.getParent() as? JetCallExpression) ?: ref
val superExpr = (selector.getParent() as? JetQualifiedExpression)?.getReceiverExpression() as? JetSuperExpression
if (superExpr != null) {
return JetRefactoringBundle.message("cannot.extract.super.call")!!
return ErrorMessage.SUPER_CALL
}
val receiverArgument = resolvedCall?.getReceiverArgument()
@@ -347,7 +347,7 @@ private fun ExtractionData.inferParametersInfo(
if (classObjectClassDescriptor != null) {
if (!classObjectClassDescriptor.canBeReferencedViaImport()) {
return JetRefactoringBundle.message("cannot.extract.non.local.declaration.ref")!!
return ErrorMessage.NON_LOCAL_DECLARATION
}
replacementMap[refInfo.offsetInBody] = FqNameReplacement(DescriptorUtils.getFqNameSafe(originalDescriptor))
@@ -435,9 +435,9 @@ private fun ExtractionData.inferParametersInfo(
if (nonDenotableTypes.isNotEmpty()) {
val typeStr = nonDenotableTypes.map {
DescriptorRenderer.HTML.renderType(it)
}.sort().makeString(separator = "\n")
}.sort()
return "${JetRefactoringBundle.message("parameter.types.are.not.denotable")!!}\n$typeStr"
return ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr)
}
parameters.addAll(extractedDescriptorToParameter.values())
@@ -449,7 +449,7 @@ private fun ExtractionData.checkLocalDeclarationsWithNonLocalUsages(
allInstructions: List<Instruction>,
localInstructions: List<Instruction>,
bindingContext: BindingContext
): String? {
): ErrorMessage? {
// todo: non-locally used declaration can be turned into the output value
val declarations = ArrayList<JetNamedDeclaration>()
@@ -465,14 +465,14 @@ private fun ExtractionData.checkLocalDeclarationsWithNonLocalUsages(
}
if (declarations.isNotEmpty()) {
val localVarStr = declarations.map { it.getName()!! }.sort().makeString(separator = "\n")
return "${JetRefactoringBundle.message("variables.are.used.outside.of.selected.code.fragment")!!}\n$localVarStr"
val localVarStr = declarations.map { it.getName()!! }.sort()
return ErrorMessage.VARIABLES_ARE_USED_OUTSIDE.addAdditionalInfo(localVarStr)
}
return null
}
private fun ExtractionData.checkDeclarationsMovingOutOfScope(controlFlow: ControlFlow): String? {
private fun ExtractionData.checkDeclarationsMovingOutOfScope(controlFlow: ControlFlow): ErrorMessage? {
val declarationsOutOfScope = HashSet<JetNamedDeclaration>()
if (controlFlow is JumpBasedControlFlow) {
controlFlow.elementToInsertAfterCall.accept(
@@ -488,8 +488,8 @@ private fun ExtractionData.checkDeclarationsMovingOutOfScope(controlFlow: Contro
}
if (declarationsOutOfScope.isNotEmpty()) {
val declStr = declarationsOutOfScope.map { it.getName()!! }.sort().makeString(separator = "\n")
return "${JetRefactoringBundle.message("declarations.will.move.out.of.scope")!!}\n$declStr"
val declStr = declarationsOutOfScope.map { it.getName()!! }.sort()
return ErrorMessage.DECLARATIONS_OUT_OF_SCOPE.addAdditionalInfo(declStr)
}
return null
@@ -497,14 +497,14 @@ private fun ExtractionData.checkDeclarationsMovingOutOfScope(controlFlow: Contro
fun ExtractionData.performAnalysis(): AnalysisResult {
if (originalElements.empty) {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(JetRefactoringBundle.message("cannot.refactor.no.expresson")!!))
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_EXPRESSION))
}
val commonParent = PsiTreeUtil.findCommonParent(originalElements)!!
val enclosingDeclaration = commonParent.getParentByType(javaClass<JetDeclarationWithBody>())
if (enclosingDeclaration == null) {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(JetRefactoringBundle.message("cannot.refactor.no.container")!!))
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_CONTAINER))
}
val resolveSession = originalFile.getLazyResolveSession()
@@ -527,7 +527,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(parameterError))
}
val messages = ArrayList<String>()
val messages = ArrayList<ErrorMessage>()
val (controlFlow, controlFlowMessage) = localInstructions.analyzeControlFlow(bindingContext, parameters, inferredResultType)
controlFlowMessage?.let { messages.add(it) }
@@ -1,7 +1,7 @@
LineBreakpoint created at syntacticError.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!;!RT_JAR! syntacticError.SyntacticErrorPackage
LineBreakpoint created at errors.kt:13
!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!;!RT_JAR! errors.ErrorsPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
syntacticError.kt:4
errors.kt:12
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,28 @@
package errors
fun main(args: Array<String>) {
MyClass().baseFun()
}
class MyClass: Base() {
override fun baseFun() {
var prop = 1
var prop2 = 2
//Breakpoint!
prop.minus(prop2)
}
}
open class Base {
open fun baseFun() {}
}
// EXPRESSION: a
// RESULT: Unresolved reference: a
// EXPRESSION: prop.
// RESULT: Expecting an element; looking at ERROR_ELEMENT '(1,6) in /fragment.kt
// EXPRESSION: super.baseFun()
// RESULT: Cannot perform an action for expression with super call
@@ -0,0 +1,5 @@
prop += 1
prop2 +=2
prop + prop2
// RESULT: Cannot perform an action because this code fragment changes more than one variable: prop, prop2
@@ -0,0 +1,3 @@
object {}
// RESULT: Cannot perform an action because following types are unavailable from debugger scope: errors.MyClass.baseFun.&lt;no name provided&gt;
@@ -1,9 +0,0 @@
package syntacticError
fun main(args: Array<String>) {
//Breakpoint!
args.size
}
// EXPRESSION: args.
// RESULT: Expecting an element; looking at ERROR_ELEMENT '(1,6) in /fragment.kt
@@ -45,7 +45,7 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestC
assert(expressions.size == expectedExpressionResults.size, "Sizes of test directives are different")
val blocks = findFilesWithBlocks(file).map { FileUtil.loadFile(it, true) }
val expectedBlockResults = blocks.map { InTextDirectivesUtils.findStringWithPrefixes(it, "// RESULT: ") ?: throw AssertionError("Couldn't find expected result for block: $it") }
val expectedBlockResults = blocks.map { InTextDirectivesUtils.findLinesWithPrefixesRemoved(it, "// RESULT: ").makeString("\n") }
createDebugProcess(path)
@@ -71,6 +71,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doTest("idea/testData/debugger/tinyApp/src/evaluate/enums.kt");
}
@TestMetadata("errors.kt")
public void testErrors() throws Exception {
doTest("idea/testData/debugger/tinyApp/src/evaluate/errors.kt");
}
@TestMetadata("extractLocalVariables.kt")
public void testExtractLocalVariables() throws Exception {
doTest("idea/testData/debugger/tinyApp/src/evaluate/extractLocalVariables.kt");
@@ -116,11 +121,6 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doTest("idea/testData/debugger/tinyApp/src/evaluate/stdlib.kt");
}
@TestMetadata("syntacticError.kt")
public void testSyntacticError() throws Exception {
doTest("idea/testData/debugger/tinyApp/src/evaluate/syntacticError.kt");
}
@TestMetadata("vars.kt")
public void testVars() throws Exception {
doTest("idea/testData/debugger/tinyApp/src/evaluate/vars.kt");