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
@@ -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;
}
}