Extract Function: Type parameters inference

This commit is contained in:
Alexey Sedunov
2014-04-23 18:59:47 +04:00
parent 399c9c1175
commit 5fc1725b33
30 changed files with 469 additions and 17 deletions
@@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.LocalTimeCounter;
import kotlin.KotlinPackage;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,6 +31,7 @@ import org.jetbrains.jet.lang.resolve.name.Name;
import org.jetbrains.jet.lexer.JetKeywordToken;
import org.jetbrains.jet.plugin.JetFileType;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -526,8 +528,10 @@ public class JetPsiFactory {
static enum State {
MODIFIERS,
NAME,
RECEIVER,
FIRST_PARAM,
REST_PARAMS,
TYPE_CONSTRAINTS,
BODY,
DONE
}
@@ -543,7 +547,7 @@ public class JetPsiFactory {
sb.append(")");
state = State.BODY;
state = State.TYPE_CONSTRAINTS;
}
private void placeFun() {
@@ -554,7 +558,7 @@ public class JetPsiFactory {
}
sb.append("fun ");
state = State.NAME;
state = State.RECEIVER;
}
@NotNull
@@ -567,23 +571,28 @@ public class JetPsiFactory {
}
@NotNull
public FunctionBuilder receiver(@NotNull String receiverType) {
public FunctionBuilder typeParams(@NotNull Collection<String> values) {
placeFun();
sb.append(receiverType).append(".");
if (!values.isEmpty()) {
sb.append(KotlinPackage.makeString(values, ", ", "<", "> ", -1, ""));
}
return this;
}
@NotNull
public FunctionBuilder noReceiver() {
placeFun();
public FunctionBuilder receiver(@NotNull String receiverType) {
assert state == State.RECEIVER;
sb.append(receiverType).append(".");
state = State.NAME;
return this;
}
@NotNull
public FunctionBuilder name(@NotNull String name) {
assert state == State.NAME;
assert state == State.NAME || state == State.RECEIVER;
sb.append(name).append("(");
state = State.FIRST_PARAM;
@@ -621,9 +630,21 @@ public class JetPsiFactory {
return this;
}
@NotNull
public FunctionBuilder typeConstraints(@NotNull Collection<String> values) {
assert state == State.TYPE_CONSTRAINTS;
if (!values.isEmpty()) {
sb.append(KotlinPackage.makeString(values, ", ", " where ", "", -1, ""));
}
state = State.BODY;
return this;
}
@NotNull
public FunctionBuilder simpleBody(@NotNull String body) {
assert state == State.BODY;
assert state == State.BODY || state == State.TYPE_CONSTRAINTS;
sb.append(" = ").append(body);
state = State.DONE;
@@ -633,7 +654,7 @@ public class JetPsiFactory {
@NotNull
public FunctionBuilder blockBody(@NotNull String body) {
assert state == State.BODY;
assert state == State.BODY || state == State.TYPE_CONSTRAINTS;
sb.append(" {\n").append(body).append("\n}");
state = State.DONE;
@@ -125,17 +125,27 @@ public class DFS {
}
}
public static abstract class NodeHandlerWithListResult<N, R> extends AbstractNodeHandler<N, List<R>> {
public static abstract class CollectingNodeHandler<N, R, C extends Iterable<R>> extends AbstractNodeHandler<N, C> {
@NotNull
protected final LinkedList<R> result = new LinkedList<R>();
protected final C result;
protected CollectingNodeHandler(@NotNull C result) {
this.result = result;
}
@Override
@NotNull
public List<R> result() {
public C result() {
return result;
}
}
public static abstract class NodeHandlerWithListResult<N, R> extends CollectingNodeHandler<N, R, LinkedList<R>> {
protected NodeHandlerWithListResult() {
super(new LinkedList<R>());
}
}
public static class TopologicalOrder<N> extends NodeHandlerWithListResult<N, N> {
@Override
public void afterChildren(N current) {
@@ -29,6 +29,8 @@ import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.plugin.references.JetSimpleNameReference.ShorteningMode
import org.jetbrains.jet.lang.psi.psiUtil.replaced
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.psi.JetTypeParameter
import org.jetbrains.jet.lang.psi.JetTypeConstraint
data class Parameter(
val argumentText: String,
@@ -40,6 +42,11 @@ data class Parameter(
val nameForRef: String get() = mirrorVarName ?: name
}
data class TypeParameter(
val originalDeclaration: JetTypeParameter,
val originalConstraints: List<JetTypeConstraint>
)
trait Replacement: Function1<JetElement, JetElement>
trait ParameterReplacement : Replacement {
@@ -120,6 +127,7 @@ data class ExtractionDescriptor(
val visibility: String,
val parameters: List<Parameter>,
val receiverParameter: Parameter?,
val typeParameters: List<TypeParameter>,
val replacementMap: Map<Int, Replacement>,
val controlFlow: ControlFlow
)
@@ -86,9 +86,20 @@ 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.AbstractNodeHandler
import org.jetbrains.jet.utils.DFS.CollectingNodeHandler
import org.jetbrains.jet.lang.resolve.BindingContextUtils
import org.jetbrains.jet.plugin.caches.resolve.getLazyResolveSession
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.descriptors.FunctionDescriptor
private val DEFAULT_FUNCTION_NAME = "myFun"
@@ -227,13 +238,75 @@ private fun ExtractionData.createTemporaryCodeBlock(): JetBlockExpression {
return tmpFunction.getBodyExpression() as JetBlockExpression
}
private fun JetType.collectReferencedTypes(): List<JetType> {
return DFS.dfsFromNode(
this,
object: Neighbors<JetType> {
override fun getNeighbors(current: JetType): Iterable<JetType> = current.getArguments().map { it.getType() }
},
VisitedWithSet(),
object: CollectingNodeHandler<JetType, JetType, ArrayList<JetType>>(ArrayList()) {
override fun afterChildren(current: JetType) {
result.add(current)
}
}
)!!
}
fun JetTypeParameter.collectRelevantConstraints(): List<JetTypeConstraint> {
val typeConstraints = getParentByType(javaClass<JetTypeParameterListOwner>())?.getTypeConstraints()
if (typeConstraints == null) return Collections.emptyList()
return typeConstraints.filter { it.getSubjectTypeParameterName()?.getReference()?.resolve() == this}
}
fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List<JetType> {
val typeRefs = ArrayList<JetTypeReference>()
originalDeclaration.getExtendsBound()?.let { typeRefs.add(it) }
originalConstraints
.map { it.getBoundTypeReference() }
.filterNotNullTo(typeRefs)
return typeRefs
.map { bindingContext[BindingContext.TYPE, it] }
.filterNotNull()
}
private fun JetType.processTypeIfExtractable(
bindingContext: BindingContext,
typeParameters: MutableSet<TypeParameter>,
nonDenotableTypes: HashSet<JetType>
): Boolean {
return collectReferencedTypes().fold(true) { (extractable, typeToCheck) ->
val parameterTypeDescriptor = typeToCheck.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor
val typeParameter = parameterTypeDescriptor?.let {
BindingContextUtils.descriptorToDeclaration(bindingContext, it)
} as? JetTypeParameter
when {
typeParameter != null -> {
typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints()))
extractable
}
typeToCheck.canBeReferencedViaImport() ->
extractable
else -> {
nonDenotableTypes.add(typeToCheck)
false
}
}
}
}
private fun ExtractionData.inferParametersInfo(
commonParent: PsiElement,
localInstructions: List<Instruction>,
bindingContext: BindingContext,
resultType: JetType?,
replacementMap: MutableMap<Int, Replacement>,
parameters: MutableSet<Parameter>
parameters: MutableSet<Parameter>,
typeParameters: MutableSet<TypeParameter>
): MaybeError<ExtractionDescriptor, String>? {
val varNameValidator = JetNameValidatorImpl(
commonParent.getParentByType(javaClass<JetExpression>()),
@@ -244,6 +317,7 @@ private fun ExtractionData.inferParametersInfo(
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
@@ -439,8 +513,10 @@ fun ExtractionData.performAnalysis(): Maybe<ExtractionDescriptor, String> {
val replacementMap = HashMap<Int, Replacement>()
val parameters = HashSet<Parameter>()
val parameterError =
inferParametersInfo(commonParent, localInstructions, bindingContext, inferredResultType, replacementMap, parameters)
val typeParameters = HashSet<TypeParameter>()
val parameterError = inferParametersInfo(
commonParent, localInstructions, bindingContext, inferredResultType, replacementMap, parameters, typeParameters
)
if (parameterError != null) return parameterError
val controlFlowInfo = localInstructions.analyzeControlFlow(bindingContext, parameters, inferredResultType)
@@ -465,7 +541,16 @@ fun ExtractionData.performAnalysis(): Maybe<ExtractionDescriptor, String> {
receiverParameter?.let { parameters.remove(it) }
return MaybeValue(
ExtractionDescriptor(this, functionName, "", parameters.sortBy { it.name }, receiverParameter, replacementMap, controlFlow)
ExtractionDescriptor(
this,
functionName,
"",
parameters.sortBy { it.name },
receiverParameter,
typeParameters.sortBy { it.originalDeclaration.getName()!! },
replacementMap,
controlFlow
)
)
}
@@ -212,6 +212,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
getVisibility(),
ContainerUtil.newArrayList(oldToNewParameters.values()),
descriptor.getReceiverParameter(),
descriptor.getTypeParameters(),
replacementMap,
controlFlow
);
@@ -0,0 +1,8 @@
// NEXT_SIBLING:
fun foo() {
open class X(val x: Int)
fun bar<T: X>(t: T): Int {
return <selection>t.x + 1</selection>
}
}
@@ -0,0 +1 @@
Cannot extract method since following types are not denotable in the target scope: foo.X
@@ -0,0 +1,8 @@
// NEXT_SIBLING:
fun foo() {
open class X(val x: Int)
fun bar<T>(t: T): Int where T: X {
return <selection>t.x + 1</selection>
}
}
@@ -0,0 +1 @@
Cannot extract method since following types are not denotable in the target scope: foo.X
@@ -0,0 +1,8 @@
open class Data(val x: Int)
class Pair<A, B>(val a: A, val b: B)
// NEXT_SIBLING:
fun foo<V: Data>(v: V): Pair<Int, V> {
return <selection>Pair(v.x + 10, v)</selection>
}
@@ -0,0 +1,12 @@
open class Data(val x: Int)
class Pair<A, B>(val a: A, val b: B)
// NEXT_SIBLING:
fun <V : Data> pair(v: V): Pair<Int, V> {
return Pair(v.x + 10, v)
}
fun foo<V: Data>(v: V): Pair<Int, V> {
return pair(v)
}
@@ -0,0 +1,9 @@
open class Data(val x: Int)
trait DataEx
class Pair<A, B>(val a: A, val b: B)
// NEXT_SIBLING:
fun foo<V: Data>(v: V): Pair<Int, V> where V: DataEx {
return <selection>Pair(v.x + 10, v)</selection>
}
@@ -0,0 +1,13 @@
open class Data(val x: Int)
trait DataEx
class Pair<A, B>(val a: A, val b: B)
// NEXT_SIBLING:
fun <V : Data> pair(v: V): Pair<Int, V> where V : DataEx {
return Pair(v.x + 10, v)
}
fun foo<V: Data>(v: V): Pair<Int, V> where V: DataEx {
return pair(v)
}
@@ -0,0 +1,8 @@
class Data<T>(val t: Int)
// NEXT_SIBLING:
class A<T> {
fun foo(d: Data<T>): Int {
return <selection>d.t + 1</selection>
}
}
@@ -0,0 +1,12 @@
class Data<T>(val t: Int)
// NEXT_SIBLING:
fun <T> i(d: Data<T>): Int {
return d.t + 1
}
class A<T> {
fun foo(d: Data<T>): Int {
return i(d)
}
}
@@ -0,0 +1,12 @@
open class Data(val x: Int)
trait DataEx
trait DataExEx
// NEXT_SIBLING:
class A<T: Data>(val t: T) where T: DataEx {
inner class B<U: Data>(val u: U) where U: DataExEx {
fun foo<V: Data>(v: V): Int where V: DataEx {
return <selection>t.x + u.x + v.x</selection>
}
}
}
@@ -0,0 +1,16 @@
open class Data(val x: Int)
trait DataEx
trait DataExEx
// NEXT_SIBLING:
fun <T : Data, U : Data, V : Data> i(a: A<T>, b: A.B<U>, v: V): Int where T : DataEx, U : DataExEx, V : DataEx {
return a.t.x + b.u.x + v.x
}
class A<T: Data>(val t: T) where T: DataEx {
inner class B<U: Data>(val u: U) where U: DataExEx {
fun foo<V: Data>(v: V): Int where V: DataEx {
return i(this@A, this@B, v)
}
}
}
@@ -0,0 +1,12 @@
open class Data(val x: Int)
trait DataEx
trait DataExEx
class A<T: Data>(val t: T) where T: DataEx {
// NEXT_SIBLING:
inner class B<U: Data>(val u: U) where U: DataExEx {
fun foo<V: Data>(v: V): Int where V: DataEx {
return <selection>t.x + u.x + v.x</selection>
}
}
}
@@ -0,0 +1,16 @@
open class Data(val x: Int)
trait DataEx
trait DataExEx
class A<T: Data>(val t: T) where T: DataEx {
// NEXT_SIBLING:
fun <U : Data, V : Data> B<U>.i(v: V): Int where U : DataExEx, V : DataEx {
return t.x + u.x + v.x
}
inner class B<U: Data>(val u: U) where U: DataExEx {
fun foo<V: Data>(v: V): Int where V: DataEx {
return i(v)
}
}
}
@@ -0,0 +1,12 @@
open class Data(val x: Int)
trait DataEx
trait DataExEx
class A<T: Data>(val t: T) where T: DataEx {
inner class B<U: Data>(val u: U) where U: DataExEx {
// NEXT_SIBLING:
fun foo<V: Data>(v: V): Int where V: DataEx {
return <selection>t.x + u.x + v.x</selection>
}
}
}
@@ -0,0 +1,16 @@
open class Data(val x: Int)
trait DataEx
trait DataExEx
class A<T: Data>(val t: T) where T: DataEx {
inner class B<U: Data>(val u: U) where U: DataExEx {
// NEXT_SIBLING:
fun <V : Data> i(v: V): Int where V : DataEx {
return t.x + u.x + v.x
}
fun foo<V: Data>(v: V): Int where V: DataEx {
return i(v)
}
}
}
@@ -0,0 +1,10 @@
open class Data(val x: Int)
// NEXT_SIBLING:
class A<T: Data>(val t: T) {
inner class B<U: Data>(val u: U) {
fun foo<V: Data>(v: V): Int {
return <selection>t.x + u.x + v.x</selection>
}
}
}
@@ -0,0 +1,14 @@
open class Data(val x: Int)
// NEXT_SIBLING:
fun <T : Data, U : Data, V : Data> i(a: A<T>, b: A.B<U>, v: V): Int {
return a.t.x + b.u.x + v.x
}
class A<T: Data>(val t: T) {
inner class B<U: Data>(val u: U) {
fun foo<V: Data>(v: V): Int {
return i(this@A, this@B, v)
}
}
}
@@ -0,0 +1,10 @@
open class Data(val x: Int)
class A<T: Data>(val t: T) {
// NEXT_SIBLING:
inner class B<U: Data>(val u: U) {
fun foo<V: Data>(v: V): Int {
return <selection>t.x + u.x + v.x</selection>
}
}
}
@@ -0,0 +1,14 @@
open class Data(val x: Int)
class A<T: Data>(val t: T) {
// NEXT_SIBLING:
fun <U : Data, V : Data> B<U>.i(v: V): Int {
return t.x + u.x + v.x
}
inner class B<U: Data>(val u: U) {
fun foo<V: Data>(v: V): Int {
return i(v)
}
}
}
@@ -0,0 +1,10 @@
open class Data(val x: Int)
class A<T: Data>(val t: T) {
inner class B<U: Data>(val u: U) {
// NEXT_SIBLING:
fun foo<V: Data>(v: V): Int {
return <selection>t.x + u.x + v.x</selection>
}
}
}
@@ -0,0 +1,14 @@
open class Data(val x: Int)
class A<T: Data>(val t: T) {
inner class B<U: Data>(val u: U) {
// NEXT_SIBLING:
fun <V : Data> i(v: V): Int {
return t.x + u.x + v.x
}
fun foo<V: Data>(v: V): Int {
return i(v)
}
}
}
@@ -0,0 +1,9 @@
open class Data(val x: Int)
trait DataEx
// NEXT_SIBLING:
class A<T: Data>(val t: T) where T: DataEx {
fun foo<V: Data>(v: V): Int {
return <selection>t.x + v.x</selection>
}
}
@@ -0,0 +1,13 @@
open class Data(val x: Int)
trait DataEx
// NEXT_SIBLING:
fun <T : Data, V : Data> A<T>.i(v: V): Int where T : DataEx {
return t.x + v.x
}
class A<T: Data>(val t: T) where T: DataEx {
fun foo<V: Data>(v: V): Int {
return i(v)
}
}
@@ -186,7 +186,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
}
@TestMetadata("idea/testData/refactoring/extractFunction")
@InnerTestClasses({ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.Parameters.class})
@InnerTestClasses({ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class})
public static class ExtractFunction extends AbstractJetExtractionTest {
public void testAllFilesPresentInExtractFunction() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction"), Pattern.compile("^(.+)\\.kt$"), true);
@@ -618,12 +618,81 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
}
}
@TestMetadata("idea/testData/refactoring/extractFunction/typeParameters")
public static class TypeParameters extends AbstractJetExtractionTest {
public void testAllFilesPresentInTypeParameters() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/typeParameters"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("localClassInBound.kt")
public void testLocalClassInBound() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/localClassInBound.kt");
}
@TestMetadata("localClassInTypeConstraint.kt")
public void testLocalClassInTypeConstraint() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/localClassInTypeConstraint.kt");
}
@TestMetadata("simpleTypeParameter.kt")
public void testSimpleTypeParameter() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/simpleTypeParameter.kt");
}
@TestMetadata("simpleTypeParameterWithConstraint.kt")
public void testSimpleTypeParameterWithConstraint() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/simpleTypeParameterWithConstraint.kt");
}
@TestMetadata("typeParamInArgument.kt")
public void testTypeParamInArgument() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParamInArgument.kt");
}
@TestMetadata("typeParametersAndConstraintsCombined1.kt")
public void testTypeParametersAndConstraintsCombined1() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined1.kt");
}
@TestMetadata("typeParametersAndConstraintsCombined2.kt")
public void testTypeParametersAndConstraintsCombined2() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined2.kt");
}
@TestMetadata("typeParametersAndConstraintsCombined3.kt")
public void testTypeParametersAndConstraintsCombined3() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined3.kt");
}
@TestMetadata("typeParametersCombined1.kt")
public void testTypeParametersCombined1() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParametersCombined1.kt");
}
@TestMetadata("typeParametersCombined2.kt")
public void testTypeParametersCombined2() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParametersCombined2.kt");
}
@TestMetadata("typeParametersCombined3.kt")
public void testTypeParametersCombined3() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParametersCombined3.kt");
}
@TestMetadata("typeParametersCombinedAndThis.kt")
public void testTypeParametersCombinedAndThis() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/typeParameters/typeParametersCombinedAndThis.kt");
}
}
public static Test innerSuite() {
TestSuite suite = new TestSuite("ExtractFunction");
suite.addTestSuite(ExtractFunction.class);
suite.addTestSuite(Basic.class);
suite.addTest(ControlFlow.innerSuite());
suite.addTest(Parameters.innerSuite());
suite.addTestSuite(TypeParameters.class);
return suite;
}
}