Quickfix for NOTHING_TO_OVERRIDE: add function to one of supertypes.

This commit is contained in:
Michał Sapalski
2013-04-22 15:26:54 +02:00
committed by Andrey Breslav
parent e96cf045dd
commit d4fc814d76
22 changed files with 504 additions and 13 deletions
@@ -209,4 +209,9 @@ property.is.implemented.header=Is implemented in <br/>
property.is.overridden.header=Is overridden in <br/>
navigation.title.overriding.property=Choose Implementation of {0}
navigation.findUsages.title.overriding.property=Overriding properties of {0}
navigation.findUsages.title.overriding.property=Overriding properties of {0}
add.function.to.supertype.family=Add Function to Supertype
add.function.to.supertype.action.multiple=Add function to supertype...
add.function.to.type.action.single=Add ''{0}'' to ''{1}''
add.function.to.type.action=Add function to type
add.function.to.type.action.type.chooser=Choose type...
@@ -0,0 +1,184 @@
/*
* Copyright 2010-2013 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.actions;
import com.intellij.codeInsight.hint.QuestionAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopupStep;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.descriptors.ClassKind;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.Modality;
import org.jetbrains.jet.lang.psi.JetClass;
import org.jetbrains.jet.lang.psi.JetClassBody;
import org.jetbrains.jet.lang.psi.JetNamedFunction;
import org.jetbrains.jet.lang.psi.JetPsiFactory;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.plugin.JetBundle;
import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils;
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil;
import org.jetbrains.jet.plugin.codeInsight.ReferenceToClassesShortening;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Changes method signature to one of provided signatures.
* Based on {@link JetAddImportAction}
*/
public class JetAddFunctionToClassifierAction implements QuestionAction {
private final List<FunctionDescriptor> functionsToAdd;
private final Project project;
private final Editor editor;
private final BindingContext bindingContext;
/**
* @param project Project where action takes place.
* @param editor Editor where modification should be done.
* @param bindingContext BindingContext to be used for finding type declarations.
* @param functionsToAdd List of possible functions to add.
*/
public JetAddFunctionToClassifierAction(
@NotNull Project project,
@NotNull Editor editor,
@NotNull BindingContext bindingContext,
@NotNull List<FunctionDescriptor> functionsToAdd
) {
this.project = project;
this.editor = editor;
this.bindingContext = bindingContext;
this.functionsToAdd = new ArrayList<FunctionDescriptor>(functionsToAdd);
}
private static void addFunction(
@NotNull final Project project,
@NotNull final ClassDescriptor typeDescriptor,
@NotNull final FunctionDescriptor functionDescriptor,
@NotNull BindingContext bindingContext
) {
final String signatureString = CodeInsightUtils.createFunctionSignatureStringFromDescriptor(
functionDescriptor,
/* shortTypeNames = */ false);
PsiDocumentManager.getInstance(project).commitAllDocuments();
final JetClass classifierDeclaration = (JetClass) DescriptorToDeclarationUtil.getDeclaration(project, typeDescriptor, bindingContext);
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
JetClassBody body = classifierDeclaration.getBody();
if (body == null) {
PsiElement whitespaceBefore = classifierDeclaration.add(JetPsiFactory.createWhiteSpace(project));
body = (JetClassBody) classifierDeclaration.addAfter(JetPsiFactory.createEmptyClassBody(project), whitespaceBefore);
classifierDeclaration.addAfter(JetPsiFactory.createNewLine(project), body);
}
String functionBody = "";
if (typeDescriptor.getKind() != ClassKind.TRAIT && functionDescriptor.getModality() != Modality.ABSTRACT) {
functionBody = "{}";
JetType returnType = functionDescriptor.getReturnType();
if (returnType == null || !KotlinBuiltIns.getInstance().isUnit(returnType)) {
functionBody = "{ throw UnsupportedOperationException() }";
}
}
JetNamedFunction functionElement = JetPsiFactory.createFunction(project, signatureString + functionBody);
PsiElement anchor = body.getRBrace();
JetNamedFunction insertedFunctionElement = (JetNamedFunction) body.addBefore(functionElement, anchor);
ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(insertedFunctionElement));
}
});
}
}, JetBundle.message("add.function.to.type.action"), null);
}
@Override
public boolean execute() {
if (functionsToAdd.isEmpty()) {
return false;
}
if (functionsToAdd.size() == 1 || !editor.getComponent().isShowing()) {
addFunction(functionsToAdd.get(0));
}
else {
chooseFunctionAndAdd();
}
return true;
}
private void chooseFunctionAndAdd() {
JBPopupFactory.getInstance().createListPopup(getFunctionPopup()).showInBestPositionFor(editor);
}
private ListPopupStep getFunctionPopup() {
return new BaseListPopupStep<FunctionDescriptor>(
JetBundle.message("add.function.to.type.action.type.chooser"), functionsToAdd) {
@Override
public boolean isAutoSelectionEnabled() {
return false;
}
@Override
public PopupStep onChosen(FunctionDescriptor selectedValue, boolean finalChoice) {
if (finalChoice) {
addFunction(selectedValue);
}
return FINAL_CHOICE;
}
@Override
public Icon getIconFor(FunctionDescriptor aValue) {
return PlatformIcons.FUNCTION_ICON;
}
@NotNull
@Override
public String getTextFor(FunctionDescriptor functionDescriptor) {
ClassDescriptor type = (ClassDescriptor) functionDescriptor.getContainingDeclaration();
return JetBundle.message("add.function.to.type.action.single",
CodeInsightUtils.createFunctionSignatureStringFromDescriptor(
functionDescriptor,
/* shortTypeNames = */ true),
type.getName().toString());
}
};
}
private void addFunction(FunctionDescriptor functionToAdd) {
addFunction(project, (ClassDescriptor) functionToAdd.getContainingDeclaration(),
functionToAdd, bindingContext);
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.jet.plugin.codeInsight;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.psi.JetFile;
@@ -30,11 +31,15 @@ public final class DescriptorToDeclarationUtil {
}
public static PsiElement getDeclaration(JetFile file, DeclarationDescriptor descriptor, BindingContext bindingContext) {
return getDeclaration(file.getProject(), descriptor, bindingContext);
}
public static PsiElement getDeclaration(Project project, DeclarationDescriptor descriptor, BindingContext bindingContext) {
Collection<PsiElement> elements = BindingContextUtils.descriptorToDeclarations(bindingContext, descriptor);
if (elements.isEmpty()) {
BuiltInsReferenceResolver libraryReferenceResolver =
file.getProject().getComponent(BuiltInsReferenceResolver.class);
project.getComponent(BuiltInsReferenceResolver.class);
elements = libraryReferenceResolver.resolveStandardLibrarySymbol(descriptor);
}
@@ -0,0 +1,171 @@
/*
* Copyright 2010-2013 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.quickfix;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.diagnostics.Diagnostic;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetNamedFunction;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.TypeUtils;
import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.plugin.JetBundle;
import org.jetbrains.jet.plugin.actions.JetAddFunctionToClassifierAction;
import org.jetbrains.jet.plugin.caches.resolve.KotlinCacheManagerUtil;
import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AddFunctionToSupertypeFix extends JetHintAction<JetNamedFunction> {
private final List<FunctionDescriptor> functionsToAdd;
public AddFunctionToSupertypeFix(JetNamedFunction element) {
super(element);
functionsToAdd = generateFunctionsToAdd(element);
}
private static List<FunctionDescriptor> generateFunctionsToAdd(JetNamedFunction functionElement) {
BindingContext context = KotlinCacheManagerUtil.getDeclarationsFromProject(functionElement).getBindingContext();
FunctionDescriptor functionDescriptor = context.get(BindingContext.FUNCTION, functionElement);
if (functionDescriptor == null) return Collections.emptyList();
DeclarationDescriptor containingDeclaration = functionDescriptor.getContainingDeclaration();
if (!(containingDeclaration instanceof ClassDescriptor)) return Collections.emptyList();
List<FunctionDescriptor> functions = Lists.newArrayList();
ClassDescriptor classDescriptor = (ClassDescriptor) containingDeclaration;
// TODO: filter out impossible supertypes (for example when argument's type isn't visible in a superclass).
for (ClassDescriptor supertypeDescriptor : getSupertypes(classDescriptor)) {
if (KotlinBuiltIns.getInstance().isAny(supertypeDescriptor.getDefaultType())) continue;
functions.add(generateFunctionSignatureForType(functionDescriptor, supertypeDescriptor));
}
return functions;
}
private static List<ClassDescriptor> getSupertypes(ClassDescriptor classDescriptor) {
List<JetType> supertypes = Lists.newArrayList(TypeUtils.getAllSupertypes(classDescriptor.getDefaultType()));
Collections.sort(supertypes, new Comparator<JetType>() {
@Override
public int compare(JetType o1, JetType o2) {
if (o1.equals(o2)) {
return 0;
}
if (JetTypeChecker.INSTANCE.isSubtypeOf(o1, o2)) {
return -1;
}
if (JetTypeChecker.INSTANCE.isSubtypeOf(o2, o1)) {
return 1;
}
return o1.toString().compareTo(o2.toString());
}
});
List<ClassDescriptor> supertypesDescriptors = Lists.newArrayList();
for (JetType supertype : supertypes) {
supertypesDescriptors.add(DescriptorUtils.getClassDescriptorForType(supertype));
}
return supertypesDescriptors;
}
private static FunctionDescriptor generateFunctionSignatureForType(
FunctionDescriptor functionDescriptor,
ClassDescriptor typeDescriptor
) {
// TODO: support for generics.
Modality modality = typeDescriptor.getModality();
if (typeDescriptor.getKind() == ClassKind.TRAIT) {
modality = Modality.OPEN;
}
return functionDescriptor.copy(
typeDescriptor,
modality,
functionDescriptor.getVisibility(),
CallableMemberDescriptor.Kind.DECLARATION,
/* copyOverrides = */ false);
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return super.isAvailable(project, editor, file) && !functionsToAdd.isEmpty();
}
@Override
public boolean showHint(Editor editor) {
return false;
}
@NotNull
@Override
public String getText() {
if (functionsToAdd.size() == 1) {
FunctionDescriptor newFunction = functionsToAdd.get(0);
ClassDescriptor supertype = (ClassDescriptor) newFunction.getContainingDeclaration();
return JetBundle.message("add.function.to.type.action.single",
CodeInsightUtils.createFunctionSignatureStringFromDescriptor(newFunction, /* shortTypeNames */ true),
supertype.getName().toString());
}
else {
return JetBundle.message("add.function.to.supertype.action.multiple");
}
}
@NotNull
@Override
public String getFamilyName() {
return JetBundle.message("add.function.to.supertype.family");
}
@Override
protected void invoke(@NotNull final Project project, final Editor editor, JetFile file) {
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
@Override
public void run() {
createAction(project, editor).execute();
}
});
}
@NotNull
private JetAddFunctionToClassifierAction createAction(Project project, Editor editor) {
BindingContext bindingContext = KotlinCacheManagerUtil.getDeclarationsFromProject(element).getBindingContext();
return new JetAddFunctionToClassifierAction(project, editor, bindingContext, functionsToAdd);
}
public static JetIntentionActionFactory createFactory() {
return new JetIntentionActionFactory() {
@Nullable
@Override
public IntentionAction createAction(Diagnostic diagnostic) {
JetNamedFunction function = QuickFixUtil.getParentElementOfType(diagnostic, JetNamedFunction.class);
return function == null ? null : new AddFunctionToSupertypeFix(function);
}
};
}
}
@@ -91,6 +91,7 @@ public class QuickFixes {
factories.put(NOTHING_TO_OVERRIDE, RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OVERRIDE_KEYWORD));
factories.put(NOTHING_TO_OVERRIDE, ChangeMemberFunctionSignatureFix.createFactory());
factories.put(NOTHING_TO_OVERRIDE, AddFunctionToSupertypeFix.createFactory());
factories.put(VIRTUAL_MEMBER_HIDDEN, AddModifierFix.createFactory(OVERRIDE_KEYWORD));
factories.put(USELESS_CAST_STATIC_ASSERT_IS_FINE, ReplaceOperationInBinaryExpressionFix.createChangeCastToStaticAssertFactory());
@@ -0,0 +1,8 @@
// "Add 'open fun f()' to 'A'" "true"
open class A {
open fun f() {
}
}
class B : A() {
<caret>override fun f() {}
}
@@ -0,0 +1,7 @@
// "Add 'abstract fun f()' to 'A'" "true"
abstract class A {
abstract fun f()
}
class B : A() {
<caret>override fun f() {}
}
@@ -0,0 +1,8 @@
// "Add 'open fun f()' to 'A'" "true"
trait A
{
open fun f()
}
class B : A {
<caret>override fun f() {}
}
@@ -0,0 +1,9 @@
// "Add 'open fun f(): Int' to 'A'" "true"
open class A {
open fun f(): Int {
throw UnsupportedOperationException()
}
}
class B : A() {
<caret>override fun f(): Int = 5
}
@@ -0,0 +1,7 @@
// "Add 'open fun f()' to 'A'" "true"
trait A {
open fun f()
}
class B : A {
<caret>override fun f() {}
}
@@ -0,0 +1,10 @@
// "Add function to supertype..." "true"
open class A {
}
open class B : A() {
open fun f() {
}
}
class C : B() {
<caret>override fun f() {}
}
@@ -0,0 +1,8 @@
// "Add function to supertype..." "true"
trait A {
open fun foo()
}
trait B {}
class C: A, B {
<caret>override fun foo() {}
}
@@ -1,11 +0,0 @@
// "Change function signature to 'override fun f(a: Int)'" "false"
// ERROR: 'f' overrides nothing
// ACTION: Remove 'override' modifier
open class A {
open fun foo() {}
fun f(a: Int) {}
}
class B : A(){
<caret>override fun f(a: String) {}
}
@@ -0,0 +1,6 @@
// "Add 'open fun f()' to 'A'" "true"
open class A {
}
class B : A() {
<caret>override fun f() {}
}
@@ -0,0 +1,6 @@
// "Add 'abstract fun f()' to 'A'" "true"
abstract class A {
}
class B : A() {
<caret>override fun f() {}
}
@@ -0,0 +1,5 @@
// "Add 'open fun f()' to 'A'" "true"
trait A
class B : A {
<caret>override fun f() {}
}
@@ -0,0 +1,6 @@
// "Add 'open fun f(): Int' to 'A'" "true"
open class A {
}
class B : A() {
<caret>override fun f(): Int = 5
}
@@ -0,0 +1,6 @@
// "Add 'open fun f()' to 'A'" "true"
trait A {
}
class B : A {
<caret>override fun f() {}
}
@@ -0,0 +1,8 @@
// "Add function to supertype..." "true"
open class A {
}
open class B : A() {
}
class C : B() {
<caret>override fun f() {}
}
@@ -0,0 +1,6 @@
// "Add function to supertype..." "true"
trait A {}
trait B {}
class C: A, B {
<caret>override fun foo() {}
}
@@ -1,5 +1,6 @@
// "Change function signature to 'override fun f(a: Int)'" "false"
// ERROR: 'f' overrides nothing
// ACTION: Add 'open fun f(a: String)' to 'A'
// ACTION: Remove 'override' modifier
open class A {
open fun foo() {}
@@ -866,6 +866,41 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
@TestMetadata("idea/testData/quickfix/override/nothingToOverride")
public static class NothingToOverride extends AbstractQuickFixTest {
@TestMetadata("beforeAddFunction.kt")
public void testAddFunction() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddFunction.kt");
}
@TestMetadata("beforeAddFunctionAbstractClass.kt")
public void testAddFunctionAbstractClass() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddFunctionAbstractClass.kt");
}
@TestMetadata("beforeAddFunctionNoBody.kt")
public void testAddFunctionNoBody() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddFunctionNoBody.kt");
}
@TestMetadata("beforeAddFunctionNonUnitReturnType.kt")
public void testAddFunctionNonUnitReturnType() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddFunctionNonUnitReturnType.kt");
}
@TestMetadata("beforeAddFunctionTrait.kt")
public void testAddFunctionTrait() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddFunctionTrait.kt");
}
@TestMetadata("beforeAddFunctionTwoSuperclasses.kt")
public void testAddFunctionTwoSuperclasses() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddFunctionTwoSuperclasses.kt");
}
@TestMetadata("beforeAddFunctionTwoTraits.kt")
public void testAddFunctionTwoTraits() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddFunctionTwoTraits.kt");
}
@TestMetadata("beforeAddParameter.kt")
public void testAddParameter() throws Exception {
doTest("idea/testData/quickfix/override/nothingToOverride/beforeAddParameter.kt");