Create from usage: Support static Java members

This commit is contained in:
Alexey Sedunov
2015-02-16 16:26:22 +03:00
parent 19fc9adffa
commit 5cd477279b
26 changed files with 192 additions and 21 deletions
@@ -769,15 +769,10 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
val newJavaMember: PsiMember = when (declaration) { val newJavaMember: PsiMember = when (declaration) {
is JetNamedFunction -> { is JetNamedFunction -> {
val method = createJavaMethod(declaration, targetClass) val method = createJavaMethod(declaration, targetClass)
method.getModifierList().setModifierProperty(PsiModifier.FINAL, false)
val modifierList = method.getModifierList()
modifierList.setModifierProperty(PsiModifier.STATIC, false)
modifierList.setModifierProperty(PsiModifier.FINAL, false)
method method
} }
is JetProperty -> { is JetProperty -> {
if (targetClass.isInterface()) return false
createJavaField(declaration, targetClass) createJavaField(declaration, targetClass)
} }
else -> return false else -> return false
@@ -785,6 +780,8 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
declaration.delete() declaration.delete()
newJavaMember.getModifierList().setModifierProperty(PsiModifier.STATIC, callableInfo.receiverTypeInfo.classObjectRequired)
JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMember); JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMember);
val descriptor = OpenFileDescriptor(project, targetClass.getContainingFile().getVirtualFile()) val descriptor = OpenFileDescriptor(project, targetClass.getContainingFile().getVirtualFile())
@@ -75,7 +75,12 @@ abstract class TypeInfo(val variance: Variance) {
override val substitutionsAllowed: Boolean = false override val substitutionsAllowed: Boolean = false
} }
class ClassObjectRequired(delegate: TypeInfo): DelegatingTypeInfo(delegate) {
override val classObjectRequired: Boolean = true
}
open val substitutionsAllowed: Boolean = true open val substitutionsAllowed: Boolean = true
open val classObjectRequired: Boolean = false
open val possibleNamesFromExpression: Array<String> get() = ArrayUtil.EMPTY_STRING_ARRAY open val possibleNamesFromExpression: Array<String> get() = ArrayUtil.EMPTY_STRING_ARRAY
abstract fun getPossibleTypes(builder: CallableBuilder): List<JetType> abstract fun getPossibleTypes(builder: CallableBuilder): List<JetType>
@@ -79,18 +79,26 @@ public class CreateCallableFromUsageFix(
val receiverInfo = callableInfo.receiverTypeInfo val receiverInfo = callableInfo.receiverTypeInfo
if (receiverInfo is TypeInfo.Empty) return !isExtension if (receiverInfo is TypeInfo.Empty) return !isExtension
// TODO: Remove after class object extensions are supported
if (isExtension && receiverInfo.classObjectRequired) return false
val file = element.getContainingFile() as JetFile val file = element.getContainingFile() as JetFile
val project = file.getProject() val project = file.getProject()
val callableBuilder = val callableBuilder =
CallableBuilderConfiguration(callableInfos, element as JetExpression, file, null, isExtension).createBuilder() CallableBuilderConfiguration(callableInfos, element as JetExpression, file, null, isExtension).createBuilder()
val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfos.first().receiverTypeInfo) val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfos.first().receiverTypeInfo)
val isProperty = callableInfos.any { it.kind == CallableKind.PROPERTY } val propertyInfo = callableInfos.firstOrNull { it is PropertyInfo } as PropertyInfo?
val isFunction = callableInfos.any { it.kind == CallableKind.FUNCTION }
return receiverTypeCandidates.any { return receiverTypeCandidates.any {
val declaration = getDeclarationIfApplicable(project, it) val declaration = getDeclarationIfApplicable(project, it)
val insertToJavaInterface = declaration is PsiClass && declaration.isInterface()
when { when {
isProperty && declaration is PsiClass && declaration.isInterface() -> false propertyInfo != null && insertToJavaInterface && (!receiverInfo.classObjectRequired || propertyInfo.writable) ->
else -> declaration != null false
isFunction && insertToJavaInterface && receiverInfo.classObjectRequired ->
false
else ->
declaration != null
} }
} }
} }
@@ -32,7 +32,6 @@ import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.JetExpression import org.jetbrains.kotlin.psi.JetExpression
import java.util.Collections import java.util.Collections
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.psi.JetClassBody import org.jetbrains.kotlin.psi.JetClassBody
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS
@@ -42,6 +41,11 @@ import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.JetAnnotationEntry import org.jetbrains.kotlin.psi.JetAnnotationEntry
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.quickfix.JetIntentionActionsFactory import org.jetbrains.kotlin.idea.quickfix.JetIntentionActionsFactory
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToDeclarationUtil
import com.intellij.psi.PsiClass
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.refactoring.*
object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactory() { object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactory() {
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction>? { override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction>? {
@@ -60,6 +64,8 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactor
else -> throw AssertionError("Unexpected diagnostic: ${diagnostic.getFactory()}") else -> throw AssertionError("Unexpected diagnostic: ${diagnostic.getFactory()}")
} as? JetExpression ?: return null } as? JetExpression ?: return null
val project = callExpr.getProject()
val calleeExpr = when (callExpr) { val calleeExpr = when (callExpr) {
is JetCallExpression -> callExpr.getCalleeExpression() is JetCallExpression -> callExpr.getCalleeExpression()
is JetSimpleNameExpression -> callExpr is JetSimpleNameExpression -> callExpr
@@ -73,16 +79,8 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactor
if (callParent is JetQualifiedExpression && callParent.getSelectorExpression() == callExpr) callParent else callExpr if (callParent is JetQualifiedExpression && callParent.getSelectorExpression() == callExpr) callParent else callExpr
val context = calleeExpr.analyze() val context = calleeExpr.analyze()
val receiver = callExpr.getCall(context)?.getExplicitReceiver() val receiver = callExpr.getCall(context)?.getExplicitReceiver() ?: ReceiverValue.NO_RECEIVER
val receiverType = getReceiverTypeInfo(context, project, receiver) ?: return null
val receiverType = when (receiver) {
null, ReceiverValue.NO_RECEIVER -> TypeInfo.Empty
is Qualifier -> {
val qualifierType = context[BindingContext.EXPRESSION_TYPE, receiver.expression] ?: return null
TypeInfo(qualifierType, Variance.IN_VARIANCE)
}
else -> TypeInfo(receiver.getType(), Variance.IN_VARIANCE)
}
val possibleContainers = val possibleContainers =
if (receiverType is TypeInfo.Empty) { if (receiverType is TypeInfo.Empty) {
@@ -121,4 +119,20 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactor
return CreateCallableFromUsageFixes(callExpr, callableInfo) return CreateCallableFromUsageFixes(callExpr, callableInfo)
} }
private fun getReceiverTypeInfo(context: BindingContext, project: Project, receiver: ReceiverValue): TypeInfo? {
return when {
!receiver.exists() -> TypeInfo.Empty
receiver is Qualifier -> {
val qualifierType = context[BindingContext.EXPRESSION_TYPE, receiver.expression]
if (qualifierType != null) return TypeInfo(qualifierType, Variance.IN_VARIANCE)
val classifier = receiver.classifier as? JavaClassDescriptor ?: return null
val javaClass = DescriptorToDeclarationUtil.getDeclaration(project, classifier) as? PsiClass
if (javaClass == null || !javaClass.canRefactor()) return null
TypeInfo.ClassObjectRequired(TypeInfo(classifier.getDefaultType(), Variance.IN_VARIANCE))
}
else -> TypeInfo(receiver.getType(), Variance.IN_VARIANCE)
}
}
} }
@@ -501,7 +501,7 @@ fun createJavaField(property: JetProperty, targetClass: PsiClass): PsiField {
with(field.getModifierList()) { with(field.getModifierList()) {
val templateModifiers = template.getModifierList() val templateModifiers = template.getModifierList()
setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true) setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true)
if (!property.isVar()) { if (!property.isVar() || targetClass.isInterface()) {
setModifierProperty(PsiModifier.FINAL, true) setModifierProperty(PsiModifier.FINAL, true)
} }
copyModifierListItems(templateModifiers, this, false) copyModifierListItems(templateModifiers, this, false)
@@ -0,0 +1,8 @@
// "Create extension function 'foo'" "false"
// ACTION: Create function 'foo'
// ACTION: Split property declaration
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.<caret>foo("1", 2)
}
@@ -0,0 +1,8 @@
import org.jetbrains.annotations.NotNull;
class J {
public static int foo(@NotNull String s, int i) {
return 0;
}
}
@@ -0,0 +1,7 @@
// "Create function 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.foo("1", 2)
}
@@ -0,0 +1,6 @@
// "Create function 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.<caret>foo("1", 2)
}
@@ -0,0 +1,7 @@
// "Create function 'foo'" "false"
// ACTION: Split property declaration
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.<caret>foo("1", 2)
}
@@ -0,0 +1,8 @@
// "Create extension property 'foo'" "false"
// ACTION: Create property 'foo'
// ACTION: Split property declaration
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.<caret>foo
}
@@ -0,0 +1,4 @@
class J {
public static final int foo;
}
@@ -0,0 +1,7 @@
// "Create property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.foo
}
@@ -0,0 +1,6 @@
// "Create property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.<caret>foo
}
@@ -0,0 +1,7 @@
// "Create property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.foo
}
@@ -0,0 +1,6 @@
// "Create property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.<caret>foo
}
@@ -0,0 +1,6 @@
// "Create property 'foo'" "false"
// ERROR: Unresolved reference: foo
fun test() {
J.<caret>foo = 1
}
@@ -355,6 +355,24 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
doTestWithExtraFile(fileName); doTestWithExtraFile(fileName);
} }
@TestMetadata("staticExtensionFunOnJavaClass.before.Main.kt")
public void testStaticExtensionFunOnJavaClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/staticExtensionFunOnJavaClass.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("staticFunOnJavaClass.before.Main.kt")
public void testStaticFunOnJavaClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/staticFunOnJavaClass.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("staticFunOnJavaInterface.before.Main.kt")
public void testStaticFunOnJavaInterface() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/staticFunOnJavaInterface.before.Main.kt");
doTestWithExtraFile(fileName);
}
} }
} }
@@ -403,6 +421,30 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
doTestWithExtraFile(fileName); doTestWithExtraFile(fileName);
} }
@TestMetadata("staticExtensionValOnJavaType.before.Main.kt")
public void testStaticExtensionValOnJavaType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/staticExtensionValOnJavaType.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("staticValOnJavaClass.before.Main.kt")
public void testStaticValOnJavaClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/staticValOnJavaClass.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("staticValOnJavaInterface.before.Main.kt")
public void testStaticValOnJavaInterface() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/staticValOnJavaInterface.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("staticVarOnJavaInterface.before.Main.kt")
public void testStaticVarOnJavaInterface() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/staticVarOnJavaInterface.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("valOnGroovyType.before.Main.kt") @TestMetadata("valOnGroovyType.before.Main.kt")
public void testValOnGroovyType() throws Exception { public void testValOnGroovyType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.Main.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.Main.kt");