Extract Function: Utilize pseudo-value usage for return type inference

This commit is contained in:
Alexey Sedunov
2014-05-30 13:06:06 +04:00
parent 164338d6f5
commit 1a7e6eab61
35 changed files with 494 additions and 145 deletions
@@ -70,6 +70,7 @@ import org.jetbrains.jet.lang.diagnostics.rendering.DefaultErrorMessages
import com.sun.jdi.request.EventRequest
import com.sun.jdi.ObjectReference
import com.intellij.debugger.engine.SuspendContext
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionOptions
object KotlinEvaluationBuilder: EvaluatorBuilder {
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
@@ -359,7 +360,9 @@ private fun getFunctionForExtractedFragment(
val targetSibling = tmpFile.getDeclarations().firstOrNull()
if (targetSibling == null) return null
val analysisResult = ExtractionData(tmpFile, Collections.singletonList(newDebugExpression), targetSibling).performAnalysis()
val analysisResult = ExtractionData(
tmpFile, Collections.singletonList(newDebugExpression), targetSibling, ExtractionOptions(false)
).performAnalysis()
if (analysisResult.status != Status.SUCCESS) {
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult))
}
@@ -44,6 +44,12 @@ import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.JetDeclaration
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
data class ExtractionOptions(val inferUnitTypeForUnusedValues: Boolean) {
class object {
val DEFAULT = ExtractionOptions(true)
}
}
data class ResolveResult(
val originalRefExpr: JetSimpleNameExpression,
val declaration: PsiNamedElement,
@@ -60,7 +66,8 @@ data class ResolvedReferenceInfo(
class ExtractionData(
val originalFile: JetFile,
val originalElements: List<PsiElement>,
val targetSibling: PsiElement
val targetSibling: PsiElement,
val options: ExtractionOptions = ExtractionOptions.DEFAULT
) {
val project: Project = originalFile.getProject()
@@ -133,9 +140,6 @@ class ExtractionData(
return referencesInfo
}
fun getInferredResultType(): JetType? =
getExpressions().last?.let { AnalyzerFacadeWithCache.getContextForElement(it)[BindingContext.EXPRESSION_TYPE, it] }
}
private fun compareDescriptors(d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean {
@@ -94,9 +94,7 @@ trait ControlFlow {
val returnType: JetType
}
object DefaultControlFlow: ControlFlow {
override val returnType: JetType get() = DEFAULT_RETURN_TYPE
}
class DefaultControlFlow(override val returnType: JetType = DEFAULT_RETURN_TYPE): ControlFlow
trait JumpBasedControlFlow : ControlFlow {
val elementsToReplace: List<JetElement>
@@ -17,85 +17,43 @@
package org.jetbrains.jet.plugin.refactoring.extractFunction
import com.intellij.psi.PsiElement
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.types.*
import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.psi.JetPsiFactory
import java.util.HashMap
import org.jetbrains.jet.lang.psi.JetParameter
import com.intellij.util.containers.MultiMap
import org.jetbrains.jet.lang.psi.JetBlockExpression
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import org.jetbrains.jet.lang.descriptors.CallableDescriptor
import org.jetbrains.jet.lang.descriptors.*
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle
import org.jetbrains.jet.lang.cfg.pseudocode.PseudocodeUtil
import org.jetbrains.jet.lang.psi.psiUtil.isInsideOf
import java.util.HashSet
import org.jetbrains.jet.lang.cfg.pseudocode.WriteValueInstruction
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
import org.jetbrains.jet.lang.psi.psiUtil.appendElement
import java.util.*
import org.jetbrains.jet.plugin.refactoring.createTempCopy
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.psi.psiUtil.prependElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.cfg.pseudocode.JetElementInstruction
import org.jetbrains.jet.lang.cfg.pseudocode.AbstractJumpInstruction
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.psi.JetThisExpression
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
import org.jetbrains.jet.lang.cfg.pseudocode.Instruction
import java.util.ArrayList
import org.jetbrains.jet.lang.cfg.pseudocode.ReadValueInstruction
import org.jetbrains.jet.lang.cfg.pseudocode.CallInstruction
import org.jetbrains.jet.lang.types.CommonSupertypes
import org.jetbrains.jet.lang.cfg.pseudocode.*
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.cfg.pseudocode.ReturnValueInstruction
import org.jetbrains.jet.lang.cfg.Label
import org.jetbrains.jet.lang.psi.JetReturnExpression
import org.jetbrains.jet.lang.psi.JetBreakExpression
import org.jetbrains.jet.lang.psi.JetContinueExpression
import org.jetbrains.jet.lang.psi.JetPsiFactory.FunctionBuilder
import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil
import org.jetbrains.jet.lang.psi.JetThrowExpression
import org.jetbrains.jet.plugin.imports.canBeReferencedViaImport
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
import org.jetbrains.jet.lang.cfg.pseudocode.LocalFunctionDeclarationInstruction
import org.jetbrains.jet.lang.descriptors.ClassKind
import org.jetbrains.jet.lang.psi.JetSuperExpression
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lang.psi.JetTreeVisitorVoid
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import org.jetbrains.jet.lang.resolve.DescriptorUtils
import com.intellij.refactoring.util.RefactoringUIUtil
import org.jetbrains.jet.lang.psi.psiUtil.replaced
import java.util.Collections
import com.intellij.psi.PsiNamedElement
import org.jetbrains.jet.lang.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.jet.lang.types.ErrorUtils
import org.jetbrains.jet.lang.psi.JetTypeParameter
import org.jetbrains.jet.lang.psi.JetTypeConstraint
import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor
import org.jetbrains.jet.utils.DFS
import org.jetbrains.jet.utils.DFS.Neighbors
import org.jetbrains.jet.utils.DFS.VisitedWithSet
import org.jetbrains.jet.utils.DFS.CollectingNodeHandler
import org.jetbrains.jet.utils.DFS.*
import org.jetbrains.jet.lang.resolve.BindingContextUtils
import org.jetbrains.jet.plugin.caches.resolve.getLazyResolveSession
import org.jetbrains.jet.lang.psi.psiUtil.prependElement
import org.jetbrains.jet.lang.psi.psiUtil.appendElement
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.diagnostics.Errors
import org.jetbrains.jet.lang.psi.JetTypeReference
import org.jetbrains.jet.lang.psi.JetTypeParameterListOwner
import org.jetbrains.jet.lang.psi.psiUtil.replaced
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.ErrorMessage
@@ -114,21 +72,29 @@ private fun List<Instruction>.getModifiedVarDescriptors(bindingContext: BindingC
private fun List<Instruction>.getExitPoints(): List<Instruction> =
filter { localInstruction -> localInstruction.getNextInstructions().any { it !in this } }
private fun List<Instruction>.getResultType(bindingContext: BindingContext): JetType {
private fun List<Instruction>.getResultType(bindingContext: BindingContext, options: ExtractionOptions): JetType {
fun instructionToType(instruction: Instruction): JetType? {
return when (instruction) {
is ReadValueInstruction -> {
PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true, bindingContext)?.getType()
val expression = when (instruction) {
is ReturnValueInstruction -> {
(instruction.getElement() as JetReturnExpression).getReturnedExpression()
}
is CallInstruction -> {
instruction.resolvedCall.getResultingDescriptor()?.getReturnType()
val callElement = instruction.resolvedCall.getCall().getCallElement() as? JetExpression
if (callElement is JetSimpleNameExpression) callElement.getParentByType(javaClass<JetExpression>(), true) else callElement
}
is ReturnValueInstruction -> {
val expression = (instruction.getElement() as JetReturnExpression).getReturnedExpression()
bindingContext[BindingContext.EXPRESSION_TYPE, expression]
is ReadValueInstruction, is OperationInstruction -> {
(instruction as JetElementInstruction).getElement() as? JetExpression
}
else -> null
}
if (expression == null) return null
if (options.inferUnitTypeForUnusedValues) {
val pseudocode = firstOrNull()?.getOwner()
if (pseudocode != null && expression.isStatement(pseudocode)) return null
}
return bindingContext[BindingContext.EXPRESSION_TYPE, expression]
}
val resultTypes = map(::instructionToType).filterNotNull()
@@ -140,68 +106,79 @@ private fun List<AbstractJumpInstruction>.checkEquivalence(checkPsi: Boolean): B
return !checkPsi || mapTo(HashSet<String?>()) { it.getElement().getText() }.size <= 1
}
private fun JetType.isMeaningful(): Boolean {
return KotlinBuiltIns.getInstance().let { builtins -> !builtins.isUnit(this) && !builtins.isNothing(this) }
}
private fun List<Instruction>.analyzeControlFlow(
bindingContext: BindingContext,
parameters: Set<Parameter>,
inferredType: JetType?
options: ExtractionOptions,
parameters: Set<Parameter>
): Pair<ControlFlow, ErrorMessage?> {
val outParameters = parameters.filterTo(HashSet<Parameter>()) { it.mirrorVarName != null }
if (outParameters.size > 1) {
val outValuesStr = outParameters.map { it.argumentText }.sort()
return Pair(
DefaultControlFlow,
ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr)
)
}
val exitPoints = getExitPoints()
val valuedReturnExits = ArrayList<ReturnValueInstruction>()
val defaultExits = ArrayList<Instruction>()
val jumpExits = ArrayList<AbstractJumpInstruction>()
exitPoints.forEach {
when (it) {
is ReturnValueInstruction -> valuedReturnExits.add(it)
val e = (it as? UnconditionalJumpInstruction)?.getElement()
val insn =
if (e != null && e !is JetBreakExpression && e !is JetContinueExpression) {
it.getPreviousInstructions().firstOrNull()
}
else it
when (insn) {
is ReturnValueInstruction -> valuedReturnExits.add(insn)
is AbstractJumpInstruction -> {
val element = it.getElement()
val element = insn.getElement()
if (element is JetReturnExpression
|| element is JetBreakExpression
|| element is JetContinueExpression) {
jumpExits.add(it)
jumpExits.add(insn)
}
else if (element !is JetThrowExpression) {
defaultExits.add(it)
defaultExits.add(insn)
}
}
else -> if (it !is LocalFunctionDeclarationInstruction) {
defaultExits.add(it)
else -> if (insn != null && insn !is LocalFunctionDeclarationInstruction) {
defaultExits.add(insn)
}
}
}
val builtins = KotlinBuiltIns.getInstance()
val hasMeaningfulType = inferredType != null && !builtins.isUnit(inferredType) && !builtins.isNothing(inferredType)
val typeOfDefaultFlow = defaultExits.getResultType(bindingContext, options)
val returnValueType = valuedReturnExits.getResultType(bindingContext, options)
val defaultControlFlow = DefaultControlFlow(if (returnValueType.isMeaningful()) returnValueType else typeOfDefaultFlow)
if (outParameters.isNotEmpty()) {
if (hasMeaningfulType || valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) {
return Pair(DefaultControlFlow, ErrorMessage.OUTPUT_AND_EXIT_POINT)
val outParameters = parameters.filterTo(HashSet<Parameter>()) { it.mirrorVarName != null }
when {
outParameters.size > 1 -> {
val outValuesStr = outParameters.map { it.argumentText }.sort()
return Pair(
defaultControlFlow,
ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr)
)
}
return Pair(ParameterUpdate(outParameters.first()), null)
outParameters.size == 1 -> {
if (returnValueType.isMeaningful() || typeOfDefaultFlow.isMeaningful() || jumpExits.isNotEmpty()) {
return Pair(defaultControlFlow, ErrorMessage.OUTPUT_AND_EXIT_POINT)
}
return Pair(ParameterUpdate(outParameters.first()), null)
}
}
val multipleExitsError = Pair(
DefaultControlFlow,
ErrorMessage.MULTIPLE_EXIT_POINTS
)
val multipleExitsError = Pair(defaultControlFlow, ErrorMessage.MULTIPLE_EXIT_POINTS)
if (hasMeaningfulType) {
if (typeOfDefaultFlow.isMeaningful()) {
if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError
return Pair(ExpressionEvaluation(inferredType!!), null)
return Pair(ExpressionEvaluation(typeOfDefaultFlow), null)
}
if (valuedReturnExits.isNotEmpty()) {
@@ -215,7 +192,7 @@ private fun List<Instruction>.analyzeControlFlow(
}
if (!valuedReturnExits.checkEquivalence(false)) return multipleExitsError
return Pair(ExpressionEvaluationWithCallSiteReturn(valuedReturnExits.getResultType(bindingContext)), null)
return Pair(ExpressionEvaluationWithCallSiteReturn(valuedReturnExits.getResultType(bindingContext, options)), null)
}
if (jumpExits.isNotEmpty()) {
@@ -226,7 +203,7 @@ private fun List<Instruction>.analyzeControlFlow(
return Pair(UnconditionalJump(elements, elements.first!!), null)
}
return Pair(DefaultControlFlow, null)
return Pair(defaultControlFlow, null)
}
fun ExtractionData.createTemporaryFunction(functionText: String): JetNamedFunction {
@@ -291,7 +268,7 @@ fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List<J
private fun JetType.processTypeIfExtractable(
bindingContext: BindingContext,
typeParameters: MutableSet<TypeParameter>,
nonDenotableTypes: HashSet<JetType>
nonDenotableTypes: MutableSet<JetType>
): Boolean {
return collectReferencedTypes().fold(true) { (extractable, typeToCheck) ->
val parameterTypeDescriptor = typeToCheck.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor
@@ -320,10 +297,10 @@ private fun ExtractionData.inferParametersInfo(
commonParent: PsiElement,
localInstructions: List<Instruction>,
bindingContext: BindingContext,
resultType: JetType?,
replacementMap: MutableMap<Int, Replacement>,
parameters: MutableSet<Parameter>,
typeParameters: MutableSet<TypeParameter>
typeParameters: MutableSet<TypeParameter>,
nonDenotableTypes: MutableSet<JetType>
): ErrorMessage? {
val varNameValidator = JetNameValidatorImpl(
commonParent.getParentByType(javaClass<JetExpression>()),
@@ -333,7 +310,6 @@ private fun ExtractionData.inferParametersInfo(
val modifiedVarDescriptors = localInstructions.getModifiedVarDescriptors(bindingContext)
val extractedDescriptorToParameter = HashMap<DeclarationDescriptor, Parameter>()
val nonDenotableTypes = HashSet<JetType>()
for (refInfo in getBrokenReferencesInfo(createTemporaryCodeBlock())) {
val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult
@@ -406,19 +382,15 @@ private fun ExtractionData.inferParametersInfo(
else existingParameter
}
else {
val parameterName: String =
val parameterName =
if (extractThis) {
JetNameSuggester.suggestNames(parameterType, varNameValidator, null).first()
}
else originalDeclaration.getName()!!
val mirrorVarName: String?
if (descriptorToExtract in modifiedVarDescriptors) {
mirrorVarName = varNameValidator.validateName(parameterName)
}
else {
mirrorVarName = null
}
val mirrorVarName = if (descriptorToExtract in modifiedVarDescriptors)
varNameValidator.validateName(parameterName)!!
else null
val argumentText =
if (hasThisReceiver && extractThis)
@@ -439,19 +411,10 @@ private fun ExtractionData.inferParametersInfo(
}
}
resultType?.processTypeIfExtractable(bindingContext, typeParameters, nonDenotableTypes)
for (typeToCheck in typeParameters.flatMapTo(HashSet<JetType>()) { it.collectReferencedTypes(bindingContext) }) {
typeToCheck.processTypeIfExtractable(bindingContext, typeParameters, nonDenotableTypes)
}
if (nonDenotableTypes.isNotEmpty()) {
val typeStr = nonDenotableTypes.map {
DescriptorRenderer.HTML.renderType(it)
}.sort()
return ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr)
}
parameters.addAll(extractedDescriptorToParameter.values())
return null
@@ -527,13 +490,12 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
it is JetElementInstruction && it.getElement().isInsideOf(originalElements)
}
val inferredResultType = getInferredResultType()
val replacementMap = HashMap<Int, Replacement>()
val parameters = HashSet<Parameter>()
val typeParameters = HashSet<TypeParameter>()
val nonDenotableTypes = HashSet<JetType>()
val parameterError = inferParametersInfo(
commonParent, localInstructions, bindingContext, inferredResultType, replacementMap, parameters, typeParameters
commonParent, localInstructions, bindingContext, replacementMap, parameters, typeParameters, nonDenotableTypes
)
if (parameterError != null) {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(parameterError))
@@ -541,17 +503,29 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
val messages = ArrayList<ErrorMessage>()
val (controlFlow, controlFlowMessage) = localInstructions.analyzeControlFlow(bindingContext, parameters, inferredResultType)
val (controlFlow, controlFlowMessage) = localInstructions.analyzeControlFlow(bindingContext, options, parameters)
controlFlowMessage?.let { messages.add(it) }
controlFlow.returnType.processTypeIfExtractable(bindingContext, typeParameters, nonDenotableTypes)
if (nonDenotableTypes.isNotEmpty()) {
val typeStr = nonDenotableTypes.map {DescriptorRenderer.HTML.renderType(it)}.sort()
return AnalysisResult(
null,
Status.CRITICAL_ERROR,
listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr))
)
}
checkLocalDeclarationsWithNonLocalUsages(pseudocode.getInstructions(), localInstructions, bindingContext)?.let { messages.add(it) }
checkDeclarationsMovingOutOfScope(controlFlow)?.let { messages.add(it) }
val functionNameValidator = JetNameValidatorImpl(
targetSibling.getParent(),
targetSibling,
JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES
)
val functionNameValidator =
JetNameValidatorImpl(
targetSibling.getParent(),
targetSibling,
JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES
)
val functionName = JetNameSuggester.suggestNames(controlFlow.returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).first()
val receiverCandidates = parameters.filterTo(HashSet<Parameter>()) { it.receiverCandidate }