Extract Function: Analysis and code generation
This commit is contained in:
@@ -67,12 +67,14 @@ public class JetControlFlowProcessor {
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Pseudocode generatePseudocode(@NotNull JetElement subroutine) {
|
||||
Pseudocode pseudocode = generate(subroutine);
|
||||
((PseudocodeImpl) pseudocode).postProcess();
|
||||
return pseudocode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Pseudocode generate(@NotNull JetElement subroutine) {
|
||||
builder.enterSubroutine(subroutine);
|
||||
CFPVisitor cfpVisitor = new CFPVisitor(builder);
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.jetbrains.jet.util.slicedmap.WritableSlice;
|
||||
import java.util.Collection;
|
||||
|
||||
public class PseudocodeUtil {
|
||||
|
||||
@NotNull
|
||||
public static Pseudocode generatePseudocode(@NotNull JetDeclaration declaration, @NotNull final BindingContext bindingContext) {
|
||||
BindingTrace mockTrace = new BindingTrace() {
|
||||
@NotNull
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.lang.resolve.ImportPath;
|
||||
import org.jetbrains.jet.lang.resolve.name.Name;
|
||||
import org.jetbrains.jet.lexer.JetKeywordToken;
|
||||
import org.jetbrains.jet.lexer.JetTokens;
|
||||
import org.jetbrains.jet.plugin.JetFileType;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -132,7 +131,7 @@ public class JetPsiFactory {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElement createWhiteSpace(Project project, String text) {
|
||||
public static PsiElement createWhiteSpace(Project project, String text) {
|
||||
JetProperty property = createProperty(project, "val" + text + "x");
|
||||
return property.findElementAt(3);
|
||||
}
|
||||
@@ -523,6 +522,135 @@ public class JetPsiFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public static class FunctionBuilder {
|
||||
static enum State {
|
||||
MODIFIERS,
|
||||
NAME,
|
||||
FIRST_PARAM,
|
||||
REST_PARAMS,
|
||||
BODY,
|
||||
DONE
|
||||
}
|
||||
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
private State state = State.MODIFIERS;
|
||||
|
||||
public FunctionBuilder() {
|
||||
}
|
||||
|
||||
private void closeParams() {
|
||||
assert state == State.FIRST_PARAM || state == State.REST_PARAMS;
|
||||
|
||||
sb.append(")");
|
||||
|
||||
state = State.BODY;
|
||||
}
|
||||
|
||||
private void placeFun() {
|
||||
assert state == State.MODIFIERS;
|
||||
|
||||
if (sb.length() != 0) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append("fun ");
|
||||
|
||||
state = State.NAME;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder modifier(@NotNull String modifier) {
|
||||
assert state == State.MODIFIERS;
|
||||
|
||||
sb.append(modifier);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder receiver(@NotNull String receiverType) {
|
||||
placeFun();
|
||||
sb.append(receiverType).append(".");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder noReceiver() {
|
||||
placeFun();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder name(@NotNull String name) {
|
||||
assert state == State.NAME;
|
||||
|
||||
sb.append(name).append("(");
|
||||
state = State.FIRST_PARAM;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder param(@NotNull String name, @NotNull String type) {
|
||||
assert state == State.FIRST_PARAM || state == State.REST_PARAMS;
|
||||
|
||||
if (state == State.REST_PARAMS) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(name).append(": ").append(type);
|
||||
if (state == State.FIRST_PARAM) {
|
||||
state = State.REST_PARAMS;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder returnType(@NotNull String type) {
|
||||
closeParams();
|
||||
sb.append(": ").append(type);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder noReturnType() {
|
||||
closeParams();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder simpleBody(@NotNull String body) {
|
||||
assert state == State.BODY;
|
||||
|
||||
sb.append(" = ").append(body);
|
||||
state = State.DONE;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionBuilder blockBody(@NotNull String body) {
|
||||
assert state == State.BODY;
|
||||
|
||||
sb.append(" {\n").append(body).append("\n}");
|
||||
state = State.DONE;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toFunctionText() {
|
||||
if (state != State.DONE) {
|
||||
state = State.DONE;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
|
||||
JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
|
||||
|
||||
@@ -104,6 +104,9 @@ public fun JetElement.outermostLastBlockElement(predicate: (JetElement) -> Boole
|
||||
public fun JetBlockExpression.appendElement(element: JetElement): JetElement =
|
||||
addAfter(element, getRBrace()!!.getPrevSibling()!!)!! as JetElement
|
||||
|
||||
public fun JetBlockExpression.prependElement(element: JetElement): JetElement =
|
||||
addBefore(element, getLBrace()!!.getNextSibling()!!)!! as JetElement
|
||||
|
||||
public fun JetElement.wrapInBlock(): JetBlockExpression {
|
||||
val block = JetPsiFactory.createEmptyBody(getProject()) as JetBlockExpression
|
||||
block.appendElement(this)
|
||||
@@ -256,4 +259,9 @@ public fun PsiDirectory.getPackage(): PsiPackage? = JavaDirectoryService.getInst
|
||||
|
||||
public fun JetModifierListOwner.isPrivate(): Boolean = hasModifier(JetTokens.PRIVATE_KEYWORD)
|
||||
|
||||
public fun PsiElement.isInsideOf(elements: Iterable<PsiElement>): Boolean = elements.any { it.isAncestor(this) }
|
||||
public fun PsiElement.isInsideOf(elements: Iterable<PsiElement>): Boolean = elements.any { it.isAncestor(this) }
|
||||
|
||||
public tailRecursive fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? {
|
||||
val parent = getParent()
|
||||
return if (parent == container) this else parent?.getOutermostParentContainedIn(container)
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import org.jetbrains.jet.lang.descriptors.ClassDescriptor
|
||||
import org.jetbrains.jet.lang.descriptors.CallableDescriptor
|
||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.jet.lang.types.JetType
|
||||
|
||||
public val DeclarationDescriptor.importableFqName: FqName?
|
||||
get() {
|
||||
@@ -53,6 +54,11 @@ public fun DeclarationDescriptor.canBeReferencedViaImport(): Boolean {
|
||||
return this is ClassDescriptor || this is ConstructorDescriptor
|
||||
}
|
||||
|
||||
public fun JetType.canBeReferencedViaImport(): Boolean {
|
||||
val descriptor = getConstructor().getDeclarationDescriptor()
|
||||
return descriptor != null && descriptor.canBeReferencedViaImport()
|
||||
}
|
||||
|
||||
public fun isInReceiverScope(referenceElement: PsiElement, referencedDescriptor: DeclarationDescriptor): Boolean {
|
||||
val isExpressionWithReceiver = referenceElement is JetSimpleNameExpression && referenceElement.getReceiverExpression() != null
|
||||
return isExpressionWithReceiver && !referencedDescriptor.isExtension
|
||||
|
||||
@@ -6,8 +6,17 @@ cannot.refactor.no.container=Cannot refactor in this place
|
||||
cannot.refactor.no.expression=Cannot perform refactoring without an expression
|
||||
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
|
||||
cannot.refactor.package.expression=Cannot introduce package reference
|
||||
extract.function=Extract Function
|
||||
cannot.extract.method=Cannot find statements to extract
|
||||
cannot.find.class.to.extract=Cannot find class to extract method
|
||||
selected.code.fragment.has.multiple.output.values=Selected code fragment has multiple output values:
|
||||
variables.are.used.outside.of.selected.code.fragment=Following variables are used outside of selected code fragment:
|
||||
selected.code.fragment.has.multiple.exit.points=Selected code fragment has multiple exit points
|
||||
selected.code.fragment.has.output.values.and.exit.points=Selected code fragment has output values as well as alternative exit points
|
||||
parameter.types.are.not.denotable=Cannot extract method since following types are not denotable in the target scope:
|
||||
declarations.will.move.out.of.scope=Following declarations won't be available outside of extracted function body:
|
||||
cannot.extract.super.call=Cannot extract super-call
|
||||
cannot.extract.non.local.declaration.ref=Cannot extract reference to non-local declaration
|
||||
cannot.refactor.expression.should.have.inferred.type=Expression should have inferred type
|
||||
cannot.refactor.synthesized.function=Cannot refactor synthesized function ''{0}''
|
||||
|
||||
@@ -26,4 +35,5 @@ refactoring.move.top.level.declaration.file.title=Choose Destination File
|
||||
refactoring.move.non.kotlin.file=Target must be a Kotlin file
|
||||
|
||||
package.private.0.will.no.longer.be.accessible.from.1=Package-private {0} will no longer be accessible from {1}
|
||||
0.uses.package.private.1={0} uses package-private {1}
|
||||
0.uses.package.private.1={0} uses package-private {1}
|
||||
0.will.no.longer.be.accessible.after.extraction={0} will no longer be accessible after extraction
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2010-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.jet.plugin.refactoring.extractFunction
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.jet.lang.psi.JetFile
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.jet.lang.psi.JetExpression
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import kotlin.properties.Delegates
|
||||
import java.util.HashMap
|
||||
import org.jetbrains.jet.plugin.codeInsight.JetFileReferencesResolver
|
||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.jet.lang.psi.JetThisExpression
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext
|
||||
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil
|
||||
import java.util.Collections
|
||||
import org.jetbrains.jet.lang.psi.JetBlockExpression
|
||||
import org.jetbrains.jet.renderer.DescriptorRenderer
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getParentByTypeAndBranch
|
||||
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.isInsideOf
|
||||
import java.util.ArrayList
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import org.jetbrains.jet.lang.psi.JetSuperExpression
|
||||
import org.jetbrains.jet.lang.types.JetType
|
||||
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
|
||||
|
||||
data class ResolveResult(
|
||||
val originalRefExpr: JetSimpleNameExpression,
|
||||
val declaration: PsiNamedElement,
|
||||
val descriptor: DeclarationDescriptor,
|
||||
val resolvedCall: ResolvedCall<*>?
|
||||
)
|
||||
|
||||
data class ResolvedReferenceInfo(
|
||||
val refExpr: JetSimpleNameExpression,
|
||||
val offsetInBody: Int,
|
||||
val resolveResult: ResolveResult
|
||||
)
|
||||
|
||||
class ExtractionData(
|
||||
val originalFile: JetFile,
|
||||
val originalElements: List<PsiElement>,
|
||||
val nextSibling: PsiElement
|
||||
) {
|
||||
val project: Project = originalFile.getProject()
|
||||
|
||||
fun getExpressions(): List<JetExpression> = originalElements.filterIsInstance(javaClass<JetExpression>())
|
||||
|
||||
fun getCodeFragmentTextRange(): TextRange? {
|
||||
val originalElements = originalElements
|
||||
return when (originalElements.size) {
|
||||
0 -> null
|
||||
1 -> originalElements.first!!.getTextRange()
|
||||
else -> {
|
||||
val from = originalElements.first!!.getTextRange()!!.getStartOffset()
|
||||
val to = originalElements.last!!.getTextRange()!!.getEndOffset()
|
||||
TextRange(from, to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCodeFragmentText(): String =
|
||||
getCodeFragmentTextRange()?.let { originalFile.getText()!!.substring(it.getStartOffset(), it.getEndOffset()) } ?: ""
|
||||
|
||||
val originalStartOffset = originalElements.first?.let { e -> e.getTextRange()!!.getStartOffset() }
|
||||
|
||||
val refOffsetToDeclaration by Delegates.lazy {
|
||||
if (originalStartOffset != null) {
|
||||
val resultMap = HashMap<Int, ResolveResult>()
|
||||
for ((ref, context) in JetFileReferencesResolver.resolve(originalFile, getExpressions())) {
|
||||
if (ref !is JetSimpleNameExpression) continue
|
||||
|
||||
val resolvedCallKey = (ref.getParent() as? JetThisExpression) ?: ref
|
||||
val resolvedCall = context[BindingContext.RESOLVED_CALL, resolvedCallKey]
|
||||
|
||||
val descriptor = context[BindingContext.REFERENCE_TARGET, ref]
|
||||
if (descriptor == null) continue
|
||||
|
||||
val declaration = DescriptorToDeclarationUtil.getDeclaration(project, descriptor, context) as? PsiNamedElement
|
||||
if (declaration == null) continue
|
||||
|
||||
val offset = ref.getTextRange()!!.getStartOffset() - originalStartOffset
|
||||
resultMap[offset] = ResolveResult(ref, declaration, descriptor, resolvedCall)
|
||||
}
|
||||
resultMap
|
||||
}
|
||||
else Collections.emptyMap<Int, ResolveResult>()
|
||||
}
|
||||
|
||||
fun getBrokenReferencesInfo(body: JetBlockExpression): List<ResolvedReferenceInfo> {
|
||||
fun compareDescriptors(d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean {
|
||||
return d1 == d2 || (d1 != null && d2 != null && DescriptorRenderer.TEXT.render(d1) == DescriptorRenderer.TEXT.render(d2))
|
||||
}
|
||||
|
||||
val startOffset = body.getStatements().first!!.getTextRange()!!.getStartOffset()
|
||||
|
||||
val referencesInfo = ArrayList<ResolvedReferenceInfo>()
|
||||
for ((ref, context) in JetFileReferencesResolver.resolve(body)) {
|
||||
if (ref !is JetSimpleNameExpression) continue
|
||||
|
||||
val parent = ref.getParent()
|
||||
if (parent is JetQualifiedExpression
|
||||
&& parent.getSelectorExpression() == ref
|
||||
&& parent.getReceiverExpression() !is JetSuperExpression) continue
|
||||
|
||||
val offset = ref.getTextRange()!!.getStartOffset() - startOffset
|
||||
refOffsetToDeclaration[offset]?.let { originalResolveResult ->
|
||||
val descriptor = context[BindingContext.REFERENCE_TARGET, ref]
|
||||
if (!compareDescriptors(originalResolveResult.descriptor, descriptor)
|
||||
&& !originalResolveResult.declaration.isInsideOf(originalElements)) {
|
||||
referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return referencesInfo
|
||||
}
|
||||
|
||||
fun getInferredResultType(): JetType? =
|
||||
getExpressions().last?.let { AnalyzerFacadeWithCache.getContextForElement(it)[BindingContext.EXPRESSION_TYPE, it] }
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2010-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.jet.plugin.refactoring.extractFunction
|
||||
|
||||
import org.jetbrains.jet.lang.types.JetType
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import org.jetbrains.jet.lang.psi.JetCallExpression
|
||||
import org.jetbrains.jet.lang.psi.JetElement
|
||||
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.jet.lang.psi.JetThisExpression
|
||||
import org.jetbrains.jet.plugin.references.JetSimpleNameReference
|
||||
import org.jetbrains.jet.lang.resolve.name.FqName
|
||||
import org.jetbrains.jet.plugin.references.JetSimpleNameReference.ShorteningMode
|
||||
|
||||
data class Parameter(
|
||||
val argumentText: String,
|
||||
val name: String,
|
||||
var mirrorVarName: String?,
|
||||
val parameterType: JetType,
|
||||
val receiverCandidate: Boolean
|
||||
) {
|
||||
val nameForRef: String get() = mirrorVarName ?: name
|
||||
}
|
||||
|
||||
trait Replacement: Function1<JetElement, Unit>
|
||||
|
||||
trait ParameterReplacement : Replacement {
|
||||
val parameter: Parameter
|
||||
fun copy(parameter: Parameter): ParameterReplacement
|
||||
}
|
||||
|
||||
class RenameReplacement(override val parameter: Parameter): ParameterReplacement {
|
||||
override fun copy(parameter: Parameter) = RenameReplacement(parameter)
|
||||
|
||||
[suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")]
|
||||
override fun invoke(e: JetElement) {
|
||||
val thisExpr = e.getParent() as? JetThisExpression
|
||||
(thisExpr ?: e).replace(JetPsiFactory.createSimpleName(e.getProject(), parameter.nameForRef))
|
||||
}
|
||||
}
|
||||
|
||||
class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement {
|
||||
override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter)
|
||||
|
||||
[suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")]
|
||||
override fun invoke(e: JetElement) {
|
||||
val selector = (e.getParent() as? JetCallExpression) ?: e
|
||||
selector.replace(JetPsiFactory.createExpression(e.getProject(), "${parameter.nameForRef}.${selector.getText()}"))
|
||||
}
|
||||
}
|
||||
|
||||
class FqNameReplacement(val fqName: FqName): Replacement {
|
||||
[suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")]
|
||||
override fun invoke(e: JetElement) {
|
||||
(e.getReference() as? JetSimpleNameReference)?.bindToFqName(fqName, ShorteningMode.NO_SHORTENING)
|
||||
}
|
||||
}
|
||||
|
||||
trait ControlFlow {
|
||||
val returnType: JetType
|
||||
}
|
||||
|
||||
object DefaultControlFlow: ControlFlow {
|
||||
override val returnType: JetType get() = DEFAULT_RETURN_TYPE
|
||||
}
|
||||
|
||||
trait JumpBasedControlFlow : ControlFlow {
|
||||
val elementsToReplace: List<JetElement>
|
||||
val elementToInsertAfterCall: JetElement
|
||||
}
|
||||
|
||||
class ConditionalJump(
|
||||
override val elementsToReplace: List<JetElement>,
|
||||
override val elementToInsertAfterCall: JetElement
|
||||
): JumpBasedControlFlow {
|
||||
override val returnType: JetType get() = KotlinBuiltIns.getInstance().getBooleanType()
|
||||
}
|
||||
|
||||
class UnconditionalJump(
|
||||
override val elementsToReplace: List<JetElement>,
|
||||
override val elementToInsertAfterCall: JetElement
|
||||
): JumpBasedControlFlow {
|
||||
override val returnType: JetType get() = KotlinBuiltIns.getInstance().getUnitType()
|
||||
}
|
||||
|
||||
class ExpressionEvaluation(override val returnType: JetType): ControlFlow
|
||||
|
||||
class ExpressionEvaluationWithCallSiteReturn(override val returnType: JetType): ControlFlow
|
||||
|
||||
class ParameterUpdate(val parameter: Parameter): ControlFlow {
|
||||
override val returnType: JetType get() = parameter.parameterType
|
||||
}
|
||||
|
||||
data class ExtractionDescriptor(
|
||||
val extractionData: ExtractionData,
|
||||
val name: String,
|
||||
val visibility: String,
|
||||
val parameters: List<Parameter>,
|
||||
val receiverParameter: Parameter?,
|
||||
val replacementMap: Map<Int, Replacement>,
|
||||
val controlFlow: ControlFlow
|
||||
)
|
||||
|
||||
class ExtractionDescriptorWithConflicts(
|
||||
val descriptor: ExtractionDescriptor,
|
||||
val conflicts: MultiMap<PsiElement, String>
|
||||
)
|
||||
+626
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
* Copyright 2010-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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.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.plugin.refactoring.JetRefactoringBundle
|
||||
import org.jetbrains.jet.lang.cfg.pseudocode.PseudocodeUtil
|
||||
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
|
||||
import org.jetbrains.jet.plugin.util.MaybeValue
|
||||
import org.jetbrains.jet.plugin.util.Maybe
|
||||
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.plugin.util.MaybeError
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.appendElement
|
||||
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.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.name.FqName
|
||||
import org.jetbrains.jet.lang.resolve.DescriptorUtils
|
||||
|
||||
private val DEFAULT_FUNCTION_NAME = "myFun"
|
||||
private val DEFAULT_RETURN_TYPE = KotlinBuiltIns.getInstance().getUnitType()
|
||||
private val DEFAULT_PARAMETER_TYPE = KotlinBuiltIns.getInstance().getAnyType()
|
||||
|
||||
private fun JetType.isDefault(): Boolean = KotlinBuiltIns.getInstance().isUnit(this)
|
||||
|
||||
private fun List<Instruction>.getModifiedVarDescriptors(bindingContext: BindingContext): Set<VariableDescriptor> {
|
||||
return this
|
||||
.map {if (it is WriteValueInstruction) PseudocodeUtil.extractVariableDescriptorIfAny(it, true, bindingContext) else null}
|
||||
.filterNotNullTo(HashSet<VariableDescriptor>())
|
||||
}
|
||||
|
||||
private fun List<Instruction>.getExitPoints(): List<Instruction> =
|
||||
filter { localInstruction -> localInstruction.getNextInstructions().any { it !in this } }
|
||||
|
||||
private fun List<Instruction>.getResultType(bindingContext: BindingContext): JetType {
|
||||
fun instructionToType(instruction: Instruction): JetType? {
|
||||
return when (instruction) {
|
||||
is ReadValueInstruction -> {
|
||||
PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true, bindingContext)?.getType()
|
||||
}
|
||||
is CallInstruction -> {
|
||||
instruction.resolvedCall.getResultingDescriptor()?.getReturnType()
|
||||
}
|
||||
is ReturnValueInstruction -> {
|
||||
val expression = (instruction.getElement() as JetReturnExpression).getReturnedExpression()
|
||||
bindingContext[BindingContext.EXPRESSION_TYPE, expression]
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val resultTypes = map(::instructionToType).filterNotNull()
|
||||
return if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE
|
||||
}
|
||||
|
||||
private fun List<AbstractJumpInstruction>.checkEquivalence(checkPsi: Boolean): Boolean {
|
||||
if (mapTo(HashSet<Label?>()) { it.getTargetLabel() }.size > 1) return false
|
||||
return !checkPsi || mapTo(HashSet<String?>()) { it.getElement().getText() }.size <= 1
|
||||
}
|
||||
|
||||
private fun List<Instruction>.analyzeControlFlow(
|
||||
bindingContext: BindingContext,
|
||||
parameters: Set<Parameter>,
|
||||
inferredType: JetType?
|
||||
): Maybe<ControlFlow, String> {
|
||||
val outParameters = parameters.filterTo(HashSet<Parameter>()) { it.mirrorVarName != null }
|
||||
|
||||
if (outParameters.size > 1) {
|
||||
val outValuesStr = outParameters.map { it.argumentText }.sort().makeString(separator = "\n")
|
||||
|
||||
return MaybeError("${JetRefactoringBundle.message("selected.code.fragment.has.multiple.output.values")!!}\n$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)
|
||||
|
||||
is AbstractJumpInstruction -> {
|
||||
val element = it.getElement()
|
||||
if (element is JetReturnExpression
|
||||
|| element is JetBreakExpression
|
||||
|| element is JetContinueExpression
|
||||
|| element is JetThrowExpression) {
|
||||
jumpExits.add(it)
|
||||
}
|
||||
else {
|
||||
defaultExits.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
else -> if (it !is LocalFunctionDeclarationInstruction) {
|
||||
defaultExits.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val builtins = KotlinBuiltIns.getInstance()
|
||||
val hasMeaningfulType = inferredType != null && !builtins.isUnit(inferredType) && !builtins.isNothing(inferredType)
|
||||
|
||||
if (outParameters.isNotEmpty()) {
|
||||
if (hasMeaningfulType || valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) {
|
||||
return MaybeError(JetRefactoringBundle.message("selected.code.fragment.has.output.values.and.exit.points")!!)
|
||||
}
|
||||
|
||||
return MaybeValue(ParameterUpdate(outParameters.first()))
|
||||
}
|
||||
|
||||
val multipleExitsError =
|
||||
MaybeError<ControlFlow, String>(JetRefactoringBundle.message("selected.code.fragment.has.multiple.exit.points")!!)
|
||||
|
||||
if (hasMeaningfulType) {
|
||||
if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError
|
||||
|
||||
return MaybeValue(ExpressionEvaluation(inferredType!!))
|
||||
}
|
||||
|
||||
if (valuedReturnExits.isNotEmpty()) {
|
||||
if (jumpExits.isNotEmpty()) return multipleExitsError
|
||||
|
||||
if (defaultExits.isNotEmpty()) {
|
||||
if (valuedReturnExits.size != 1) return multipleExitsError
|
||||
|
||||
val element = valuedReturnExits.first!!.getElement()
|
||||
return MaybeValue(ConditionalJump(listOf(element), element))
|
||||
}
|
||||
|
||||
if (!valuedReturnExits.checkEquivalence(false)) return multipleExitsError
|
||||
return MaybeValue(ExpressionEvaluationWithCallSiteReturn(valuedReturnExits.getResultType(bindingContext)))
|
||||
}
|
||||
|
||||
if (jumpExits.isNotEmpty()) {
|
||||
if (!jumpExits.checkEquivalence(true)) return multipleExitsError
|
||||
|
||||
val elements = jumpExits.map { it.getElement() }
|
||||
if (defaultExits.isNotEmpty()) return MaybeValue(ConditionalJump(elements, elements.first!!))
|
||||
return MaybeValue(UnconditionalJump(elements, elements.first!!))
|
||||
}
|
||||
|
||||
return MaybeValue(DefaultControlFlow)
|
||||
}
|
||||
|
||||
private fun ExtractionData.createTemporaryCodeBlock(): JetBlockExpression {
|
||||
val position = nextSibling.getTextRange()!!.getStartOffset()
|
||||
val tmpFile = originalFile.createTempCopy { text ->
|
||||
StringBuilder(text).insert(position, "fun() {\n${getCodeFragmentText()}\n}\n").toString()
|
||||
}
|
||||
val tmpFunction = tmpFile.findElementAt(position)?.getParentByType(javaClass<JetNamedFunction>())!!
|
||||
return tmpFunction.getBodyExpression() as JetBlockExpression
|
||||
}
|
||||
|
||||
private fun ExtractionData.inferParametersInfo(
|
||||
commonParent: PsiElement,
|
||||
localInstructions: List<Instruction>,
|
||||
bindingContext: BindingContext,
|
||||
resultType: JetType?,
|
||||
replacementMap: MutableMap<Int, Replacement>,
|
||||
parameters: MutableSet<Parameter>
|
||||
): MaybeError<ExtractionDescriptor, String>? {
|
||||
val varNameValidator = JetNameValidatorImpl(commonParent.getParentByType(javaClass<JetExpression>()), originalElements.first, true)
|
||||
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
|
||||
val ref = refInfo.refExpr
|
||||
|
||||
val selector = (ref.getParent() as? JetCallExpression) ?: ref
|
||||
val superExpr = (selector.getParent() as? JetQualifiedExpression)?.getReceiverExpression() as? JetSuperExpression
|
||||
if (superExpr != null) {
|
||||
return MaybeError(JetRefactoringBundle.message("cannot.extract.super.call")!!)
|
||||
}
|
||||
|
||||
val receiverArgument = resolvedCall?.getReceiverArgument()
|
||||
val receiver = when(receiverArgument) {
|
||||
ReceiverValue.NO_RECEIVER -> resolvedCall?.getThisObject()
|
||||
else -> receiverArgument
|
||||
} ?: ReceiverValue.NO_RECEIVER
|
||||
|
||||
val thisDescriptor = (receiver as? ThisReceiver)?.getDeclarationDescriptor()
|
||||
val hasThisReceiver = thisDescriptor != null
|
||||
val thisExpr = ref.getParent() as? JetThisExpression
|
||||
|
||||
val hasClassObjectReceiver = (thisDescriptor as? ClassDescriptor)?.getKind() == ClassKind.CLASS_OBJECT
|
||||
val classObjectClassDescriptor =
|
||||
if (hasClassObjectReceiver) thisDescriptor!!.getContainingDeclaration() as? ClassDescriptor else null
|
||||
|
||||
if (classObjectClassDescriptor != null) {
|
||||
if (!classObjectClassDescriptor.canBeReferencedViaImport()) {
|
||||
return MaybeError(JetRefactoringBundle.message("cannot.extract.non.local.declaration.ref")!!)
|
||||
}
|
||||
|
||||
replacementMap[refInfo.offsetInBody] = FqNameReplacement(DescriptorUtils.getFqNameSafe(originalDescriptor))
|
||||
}
|
||||
else {
|
||||
val extractThis = hasThisReceiver || thisExpr != null
|
||||
val extractLocalVar =
|
||||
(originalDeclaration is JetProperty && originalDeclaration.isLocal()) || originalDeclaration is JetParameter
|
||||
|
||||
val descriptorToExtract = (if (extractThis) thisDescriptor else null) ?: originalDescriptor
|
||||
|
||||
val extractParameter = extractThis || extractLocalVar
|
||||
if (extractParameter) {
|
||||
val parameterType =
|
||||
if (hasThisReceiver) {
|
||||
when (descriptorToExtract) {
|
||||
is ClassDescriptor -> descriptorToExtract.getDefaultType()
|
||||
is CallableDescriptor -> descriptorToExtract.getReceiverParameter()?.getType()
|
||||
else -> null
|
||||
} ?: DEFAULT_PARAMETER_TYPE
|
||||
}
|
||||
else bindingContext[BindingContext.EXPRESSION_TYPE, originalRef] ?: DEFAULT_PARAMETER_TYPE
|
||||
if (!parameterType.canBeReferencedViaImport()) {
|
||||
nonDenotableTypes.add(parameterType)
|
||||
continue
|
||||
}
|
||||
|
||||
val existingParameter = extractedDescriptorToParameter[descriptorToExtract]
|
||||
val parameter: Parameter =
|
||||
if (existingParameter != null) {
|
||||
if (!JetTypeChecker.INSTANCE.equalTypes(existingParameter.parameterType, parameterType)) {
|
||||
val newParameter = existingParameter.copy(
|
||||
parameterType = CommonSupertypes.commonSupertype(listOf(existingParameter.parameterType, parameterType))
|
||||
)
|
||||
|
||||
extractedDescriptorToParameter[descriptorToExtract] = newParameter
|
||||
|
||||
for ((offset, replacement) in replacementMap) {
|
||||
if (replacement is ParameterReplacement && replacement.parameter == existingParameter) {
|
||||
replacementMap[offset] = replacement.copy(newParameter)
|
||||
}
|
||||
}
|
||||
|
||||
newParameter
|
||||
}
|
||||
else existingParameter
|
||||
}
|
||||
else {
|
||||
val parameterName: String =
|
||||
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 argumentText =
|
||||
if (hasThisReceiver && extractThis)
|
||||
"this@${parameterType.getConstructor().getDeclarationDescriptor()!!.getName().asString()}"
|
||||
else
|
||||
(thisExpr ?: ref).getText()!!
|
||||
|
||||
val parameter = Parameter(argumentText, parameterName, mirrorVarName, parameterType, extractThis)
|
||||
|
||||
extractedDescriptorToParameter[descriptorToExtract] = parameter
|
||||
|
||||
parameter
|
||||
}
|
||||
|
||||
replacementMap[refInfo.offsetInBody] =
|
||||
if (hasThisReceiver && extractThis) AddPrefixReplacement(parameter) else RenameReplacement(parameter)
|
||||
}
|
||||
else if (originalDeclaration is JetProperty) {
|
||||
return MaybeError(JetRefactoringBundle.message("cannot.extract.non.local.declaration.ref")!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resultType != null && !resultType.canBeReferencedViaImport()) {
|
||||
nonDenotableTypes.add(resultType)
|
||||
}
|
||||
|
||||
if (nonDenotableTypes.isNotEmpty()) {
|
||||
val typeStr = nonDenotableTypes.map {
|
||||
DescriptorRenderer.HTML.renderType(it)
|
||||
}.sort().makeString(separator = "\n")
|
||||
|
||||
return MaybeError("${JetRefactoringBundle.message("parameter.types.are.not.denotable")!!}\n$typeStr")
|
||||
}
|
||||
|
||||
parameters.addAll(extractedDescriptorToParameter.values())
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun ExtractionData.checkLocalDeclarationsWithNonLocalUsages(
|
||||
allInstructions: List<Instruction>,
|
||||
localInstructions: List<Instruction>,
|
||||
bindingContext: BindingContext
|
||||
): MaybeError<ExtractionDescriptor, String>? {
|
||||
// todo: non-locally used declaration can be turned into the output value
|
||||
|
||||
val declarations = ArrayList<JetNamedDeclaration>()
|
||||
for (instruction in allInstructions) {
|
||||
if (instruction in localInstructions) continue
|
||||
|
||||
PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true, bindingContext)?.let { descriptor ->
|
||||
val declaration = DescriptorToDeclarationUtil.getDeclaration(project, descriptor, bindingContext)
|
||||
if (declaration is JetNamedDeclaration && declaration.isInsideOf(originalElements)) {
|
||||
declarations.add(declaration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (declarations.isNotEmpty()) {
|
||||
val localVarStr = declarations.map { it.getName()!! }.sort().makeString(separator = "\n")
|
||||
return MaybeError("${JetRefactoringBundle.message("variables.are.used.outside.of.selected.code.fragment")!!}\n$localVarStr")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun ExtractionData.checkDeclarationsMovingOutOfScope(controlFlow: ControlFlow): MaybeError<ExtractionDescriptor, String>? {
|
||||
val declarationsOutOfScope = HashSet<JetNamedDeclaration>()
|
||||
if (controlFlow is JumpBasedControlFlow) {
|
||||
controlFlow.elementToInsertAfterCall.accept(
|
||||
object: JetTreeVisitorVoid() {
|
||||
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
|
||||
val target = expression.getReference()?.resolve()
|
||||
if (target is JetNamedDeclaration && target.isInsideOf(originalElements)) {
|
||||
declarationsOutOfScope.add(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (declarationsOutOfScope.isNotEmpty()) {
|
||||
val declStr = declarationsOutOfScope.map { it.getName()!! }.sort().makeString(separator = "\n")
|
||||
return MaybeError("${JetRefactoringBundle.message("declarations.will.move.out.of.scope")!!}\n$declStr")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun ExtractionData.performAnalysis(): Maybe<ExtractionDescriptor, String> {
|
||||
if (originalElements.empty) return MaybeError(JetRefactoringBundle.message("cannot.refactor.no.expresson")!!)
|
||||
|
||||
val commonParent = PsiTreeUtil.findCommonParent(originalElements)!!
|
||||
|
||||
val enclosingDeclaration = commonParent.getParentByType(javaClass<JetDeclarationWithBody>())
|
||||
if (enclosingDeclaration == null) return MaybeError(JetRefactoringBundle.message("cannot.refactor.no.container")!!)
|
||||
|
||||
val resolveSession = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(originalFile)
|
||||
val bindingContext = resolveSession.resolveToElement(enclosingDeclaration.getBodyExpression())
|
||||
|
||||
val pseudocode = PseudocodeUtil.generatePseudocode(enclosingDeclaration, bindingContext)
|
||||
val localInstructions = pseudocode.getInstructions().filter {
|
||||
it is JetElementInstruction && it.getElement().isInsideOf(originalElements)
|
||||
}
|
||||
|
||||
val inferredResultType = getInferredResultType()
|
||||
|
||||
val replacementMap = HashMap<Int, Replacement>()
|
||||
val parameters = HashSet<Parameter>()
|
||||
val parameterError =
|
||||
inferParametersInfo(commonParent, localInstructions, bindingContext, inferredResultType, replacementMap, parameters)
|
||||
if (parameterError != null) return parameterError
|
||||
|
||||
val controlFlowInfo = localInstructions.analyzeControlFlow(bindingContext, parameters, inferredResultType)
|
||||
if (controlFlowInfo is MaybeError) return MaybeError(controlFlowInfo.error)
|
||||
val controlFlow = (controlFlowInfo as MaybeValue).value
|
||||
|
||||
val localDeclError = checkLocalDeclarationsWithNonLocalUsages(pseudocode.getInstructions(), localInstructions, bindingContext)
|
||||
if (localDeclError != null) return localDeclError
|
||||
|
||||
val outOfScopeError = checkDeclarationsMovingOutOfScope(controlFlow)
|
||||
if (outOfScopeError != null) return outOfScopeError
|
||||
|
||||
val functionNameValidator = JetNameValidatorImpl(nextSibling.getParent(), nextSibling, false)
|
||||
val functionName = JetNameSuggester.suggestNames(controlFlow.returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).first()
|
||||
|
||||
val receiverCandidates = parameters.filterTo(HashSet<Parameter>()) { it.receiverCandidate }
|
||||
val receiverParameter = if (receiverCandidates.size == 1) receiverCandidates.first() else null
|
||||
receiverParameter?.let { parameters.remove(it) }
|
||||
|
||||
return MaybeValue(
|
||||
ExtractionDescriptor(this, functionName, "", parameters.sortBy { it.name }, receiverParameter, replacementMap, controlFlow)
|
||||
)
|
||||
}
|
||||
|
||||
fun ExtractionDescriptor.validate(): ExtractionDescriptorWithConflicts {
|
||||
// todo: add name conflict checks (parameters, function, etc.)
|
||||
return ExtractionDescriptorWithConflicts(this, MultiMap<PsiElement, String>())
|
||||
}
|
||||
|
||||
fun ExtractionDescriptor.getFunctionText(
|
||||
withBody: Boolean = true,
|
||||
descriptorRenderer: DescriptorRenderer = DescriptorRenderer.TEXT
|
||||
): String {
|
||||
return FunctionBuilder().let { builder ->
|
||||
builder.modifier(visibility)
|
||||
|
||||
receiverParameter?.let { builder.receiver(descriptorRenderer.renderType(it.parameterType)) } ?: builder.noReceiver()
|
||||
|
||||
builder.name(name)
|
||||
|
||||
parameters.forEach { parameter ->
|
||||
builder.param(parameter.name, descriptorRenderer.renderType(parameter.parameterType))
|
||||
}
|
||||
|
||||
with(controlFlow.returnType) {
|
||||
if (isDefault()) builder.noReturnType() else builder.returnType(descriptorRenderer.renderType(this))
|
||||
}
|
||||
|
||||
if (withBody) {
|
||||
builder.blockBody(extractionData.getCodeFragmentText())
|
||||
}
|
||||
|
||||
builder.toFunctionText()
|
||||
}
|
||||
}
|
||||
|
||||
fun ExtractionDescriptor.generateFunction(inTempFile: Boolean = false): JetNamedFunction {
|
||||
val project = extractionData.project
|
||||
|
||||
fun createFunction(): JetNamedFunction {
|
||||
return with(extractionData) {
|
||||
if (inTempFile) {
|
||||
val position = nextSibling.getTextRange()!!.getStartOffset()
|
||||
val tmpFile = originalFile.createTempCopy { text ->
|
||||
StringBuilder(text).insert(position, getFunctionText() + "\n").toString()
|
||||
}
|
||||
tmpFile.findElementAt(position)?.getParentByType(javaClass<JetNamedFunction>())!!
|
||||
}
|
||||
else {
|
||||
JetPsiFactory.createFunction(project, getFunctionText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun adjustFunctionBody(function: JetNamedFunction) {
|
||||
val body = function.getBodyExpression() as JetBlockExpression
|
||||
|
||||
val exprReplacementMap = HashMap<JetElement, (JetElement) -> Unit>()
|
||||
|
||||
val range = body.getStatements().first?.getTextRange()
|
||||
if (range == null) return
|
||||
|
||||
val bodyOffset = range.getStartOffset()
|
||||
val file = body.getContainingFile()!!
|
||||
|
||||
for ((offsetInBody, replacement) in replacementMap) {
|
||||
if (replacement is ParameterReplacement && replacement.parameter == receiverParameter) continue
|
||||
|
||||
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(javaClass<JetSimpleNameExpression>())
|
||||
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
|
||||
|
||||
exprReplacementMap[expr!!] = replacement
|
||||
}
|
||||
|
||||
if (controlFlow is JumpBasedControlFlow) {
|
||||
val replacementExpr = JetPsiFactory.createExpression(
|
||||
project,
|
||||
if (controlFlow is ConditionalJump) "return true" else "return"
|
||||
)
|
||||
for (jumpElement in controlFlow.elementsToReplace) {
|
||||
val offsetInBody = jumpElement.getTextRange()!!.getStartOffset() - extractionData.originalStartOffset!!
|
||||
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(jumpElement.javaClass)
|
||||
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
|
||||
|
||||
exprReplacementMap[expr!!] = { it.replace(replacementExpr) }
|
||||
}
|
||||
}
|
||||
|
||||
for ((expr, replacement) in exprReplacementMap) {
|
||||
replacement(expr)
|
||||
}
|
||||
|
||||
for (param in parameters) {
|
||||
param.mirrorVarName?.let { varName ->
|
||||
body.prependElement(JetPsiFactory.createProperty(project, varName, null, true, param.name))
|
||||
}
|
||||
}
|
||||
|
||||
when (controlFlow) {
|
||||
is ParameterUpdate ->
|
||||
body.appendElement(JetPsiFactory.createReturn(project, controlFlow.parameter.nameForRef))
|
||||
|
||||
is ConditionalJump ->
|
||||
body.appendElement(JetPsiFactory.createReturn(project, "false"))
|
||||
|
||||
is ExpressionEvaluation ->
|
||||
body.getStatements().last?.let { it.replace(JetPsiFactory.createReturn(project, it.getText()!!)) }
|
||||
}
|
||||
}
|
||||
|
||||
fun insertFunction(function: JetNamedFunction): JetNamedFunction {
|
||||
return with(extractionData) {
|
||||
val targetContainer = nextSibling.getParent()!!
|
||||
val functionInFile = targetContainer.addBefore(function, nextSibling) as JetNamedFunction
|
||||
targetContainer.addBefore(JetPsiFactory.createWhiteSpace(project, "\n\n"), nextSibling)
|
||||
|
||||
functionInFile
|
||||
}
|
||||
}
|
||||
|
||||
fun makeCall(function: JetNamedFunction): JetNamedFunction {
|
||||
val anchor = extractionData.originalElements.first
|
||||
if (anchor == null) return function
|
||||
|
||||
anchor.getNextSibling()?.let { from ->
|
||||
val to = extractionData.originalElements.last
|
||||
if (to != anchor) {
|
||||
anchor.getParent()!!.deleteChildRange(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
val callText = parameters
|
||||
.map { it.argumentText }
|
||||
.makeString(separator = ", ", prefix = "$name(", postfix = ")")
|
||||
val wrappedCall = when (controlFlow) {
|
||||
is ExpressionEvaluationWithCallSiteReturn ->
|
||||
JetPsiFactory.createReturn(project, callText)
|
||||
|
||||
is ParameterUpdate ->
|
||||
JetPsiFactory.createExpression(project, "${controlFlow.parameter.argumentText} = $callText")
|
||||
|
||||
is ConditionalJump ->
|
||||
JetPsiFactory.createExpression(project, "if ($callText) ${controlFlow.elementToInsertAfterCall.getText()}")
|
||||
|
||||
is UnconditionalJump -> {
|
||||
val anchorParent = anchor.getParent()!!
|
||||
anchorParent.addAfter(
|
||||
JetPsiFactory.createExpression(project, controlFlow.elementToInsertAfterCall.getText()),
|
||||
anchor
|
||||
)
|
||||
anchorParent.addAfter(JetPsiFactory.createNewLine(project), anchor)
|
||||
|
||||
JetPsiFactory.createExpression(project, callText)
|
||||
}
|
||||
|
||||
else ->
|
||||
JetPsiFactory.createExpression(project, callText)
|
||||
}
|
||||
anchor.replace(wrappedCall)
|
||||
|
||||
return function
|
||||
}
|
||||
|
||||
val function = createFunction()
|
||||
adjustFunctionBody(function)
|
||||
|
||||
if (inTempFile) return function
|
||||
|
||||
val functionInPlace = makeCall(insertFunction(function))
|
||||
ShortenReferences.process(functionInPlace)
|
||||
return functionInPlace
|
||||
}
|
||||
@@ -16,25 +16,17 @@
|
||||
|
||||
package org.jetbrains.jet.plugin.refactoring
|
||||
|
||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.jet.lang.resolve.name.FqName
|
||||
import org.jetbrains.jet.lang.psi.JetElement
|
||||
import org.jetbrains.jet.lang.psi.JetCallExpression
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElement
|
||||
import org.jetbrains.jet.lang.psi.JetUserType
|
||||
import org.jetbrains.jet.lang.resolve.name.isOneSegmentFQN
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import org.jetbrains.jet.lang.psi.JetFile
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil
|
||||
import com.intellij.psi.PsiPackage
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiMember
|
||||
import org.jetbrains.jet.lang.psi.JetPsiUtil
|
||||
import org.jetbrains.jet.asJava.namedUnwrappedElement
|
||||
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.util.ConflictsUtil
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getPackage
|
||||
@@ -46,6 +38,17 @@ import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.jet.lang.psi.*
|
||||
import java.util.ArrayList
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.refactoring.ui.ConflictsDialog
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import org.jetbrains.jet.lexer.JetTokens
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
|
||||
import org.jetbrains.jet.plugin.codeInsight.TipsManager
|
||||
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
|
||||
|
||||
/**
|
||||
* Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName
|
||||
@@ -120,4 +123,11 @@ public fun PsiElement.getUsageContext(): PsiElement {
|
||||
}
|
||||
|
||||
public fun PsiElement.isInJavaSourceRoot(): Boolean =
|
||||
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile())
|
||||
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile())
|
||||
|
||||
public inline fun JetFile.createTempCopy(textTransform: (String) -> String): JetFile {
|
||||
val tmpFile = JetPsiFactory.createFile(getProject(), getName(), textTransform(getText() ?: ""))
|
||||
tmpFile.setOriginalFile(this)
|
||||
return tmpFile
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user