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) {
is JetNamedFunction -> {
val method = createJavaMethod(declaration, targetClass)
val modifierList = method.getModifierList()
modifierList.setModifierProperty(PsiModifier.STATIC, false)
modifierList.setModifierProperty(PsiModifier.FINAL, false)
method.getModifierList().setModifierProperty(PsiModifier.FINAL, false)
method
}
is JetProperty -> {
if (targetClass.isInterface()) return false
createJavaField(declaration, targetClass)
}
else -> return false
@@ -785,6 +780,8 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
declaration.delete()
newJavaMember.getModifierList().setModifierProperty(PsiModifier.STATIC, callableInfo.receiverTypeInfo.classObjectRequired)
JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMember);
val descriptor = OpenFileDescriptor(project, targetClass.getContainingFile().getVirtualFile())
@@ -75,7 +75,12 @@ abstract class TypeInfo(val variance: Variance) {
override val substitutionsAllowed: Boolean = false
}
class ClassObjectRequired(delegate: TypeInfo): DelegatingTypeInfo(delegate) {
override val classObjectRequired: Boolean = true
}
open val substitutionsAllowed: Boolean = true
open val classObjectRequired: Boolean = false
open val possibleNamesFromExpression: Array<String> get() = ArrayUtil.EMPTY_STRING_ARRAY
abstract fun getPossibleTypes(builder: CallableBuilder): List<JetType>
@@ -79,18 +79,26 @@ public class CreateCallableFromUsageFix(
val receiverInfo = callableInfo.receiverTypeInfo
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 project = file.getProject()
val callableBuilder =
CallableBuilderConfiguration(callableInfos, element as JetExpression, file, null, isExtension).createBuilder()
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 {
val declaration = getDeclarationIfApplicable(project, it)
val insertToJavaInterface = declaration is PsiClass && declaration.isInterface()
when {
isProperty && declaration is PsiClass && declaration.isInterface() -> false
else -> declaration != null
propertyInfo != null && insertToJavaInterface && (!receiverInfo.classObjectRequired || propertyInfo.writable) ->
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.JetExpression
import java.util.Collections
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.psi.JetClassBody
import org.jetbrains.kotlin.psi.JetFile
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.idea.caches.resolve.analyze
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() {
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction>? {
@@ -60,6 +64,8 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactor
else -> throw AssertionError("Unexpected diagnostic: ${diagnostic.getFactory()}")
} as? JetExpression ?: return null
val project = callExpr.getProject()
val calleeExpr = when (callExpr) {
is JetCallExpression -> callExpr.getCalleeExpression()
is JetSimpleNameExpression -> callExpr
@@ -73,16 +79,8 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactor
if (callParent is JetQualifiedExpression && callParent.getSelectorExpression() == callExpr) callParent else callExpr
val context = calleeExpr.analyze()
val receiver = callExpr.getCall(context)?.getExplicitReceiver()
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 receiver = callExpr.getCall(context)?.getExplicitReceiver() ?: ReceiverValue.NO_RECEIVER
val receiverType = getReceiverTypeInfo(context, project, receiver) ?: return null
val possibleContainers =
if (receiverType is TypeInfo.Empty) {
@@ -121,4 +119,20 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetIntentionActionsFactor
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()) {
val templateModifiers = template.getModifierList()
setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true)
if (!property.isVar()) {
if (!property.isVar() || targetClass.isInterface()) {
setModifierProperty(PsiModifier.FINAL, true)
}
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);
}
@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);
}
@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")
public void testValOnGroovyType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.Main.kt");