Converted to Kotlin
This commit is contained in:
@@ -1,414 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.kotlin.idea.quickfix;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Queues;
|
||||
import com.intellij.codeInsight.hint.HintManager;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
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.PopupStep;
|
||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||
import org.jetbrains.kotlin.idea.JetBundle;
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
|
||||
import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil;
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences;
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
|
||||
import org.jetbrains.kotlin.name.Name;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier;
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRendererOptions;
|
||||
import org.jetbrains.kotlin.renderer.NameShortness;
|
||||
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil;
|
||||
import org.jetbrains.kotlin.resolve.VisibilityUtil;
|
||||
import org.jetbrains.kotlin.resolve.scopes.JetScope;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
import org.jetbrains.kotlin.types.TypeUtils;
|
||||
import org.jetbrains.kotlin.types.checker.JetTypeChecker;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Fix that changes member function's signature to match one of super functions' signatures.
|
||||
*/
|
||||
public class ChangeMemberFunctionSignatureFix extends JetHintAction<JetNamedFunction> {
|
||||
private static final DescriptorRenderer SIGNATURE_SOURCE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE.withOptions(
|
||||
new Function1<DescriptorRendererOptions, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(DescriptorRendererOptions options) {
|
||||
options.setRenderDefaultValues(false);
|
||||
return Unit.INSTANCE$;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
private static final DescriptorRenderer SIGNATURE_PREVIEW_RENDERER = DescriptorRenderer.Companion.withOptions(
|
||||
new Function1<DescriptorRendererOptions, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(DescriptorRendererOptions options) {
|
||||
options.setTypeNormalizer(IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES);
|
||||
options.setWithDefinedIn(false);
|
||||
options.setModifiers(Collections.<DescriptorRendererModifier>emptySet());
|
||||
options.setNameShortness(NameShortness.SHORT);
|
||||
options.setUnitReturnType(false);
|
||||
options.setRenderDefaultValues(false);
|
||||
return Unit.INSTANCE$;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
private final List<FunctionDescriptor> possibleSignatures;
|
||||
|
||||
public ChangeMemberFunctionSignatureFix(@NotNull JetNamedFunction element) {
|
||||
super(element);
|
||||
this.possibleSignatures = computePossibleSignatures(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
return super.isAvailable(project, editor, file) && !possibleSignatures.isEmpty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
if (possibleSignatures.size() == 1) {
|
||||
return JetBundle.message("change.function.signature.action.single", SIGNATURE_PREVIEW_RENDERER.render(possibleSignatures.get(0)));
|
||||
}
|
||||
else {
|
||||
return JetBundle.message("change.function.signature.action.multiple");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return JetBundle.message("change.function.signature.family");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(@NotNull final Project project, final Editor editor, JetFile file) throws IncorrectOperationException {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new MyAction(project, editor, getElement(), possibleSignatures).execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes all the signatures a 'functionElement' could be changed to in order to remove NOTHING_TO_OVERRIDE error.
|
||||
*/
|
||||
@NotNull
|
||||
private static List<FunctionDescriptor> computePossibleSignatures(@NotNull JetNamedFunction functionElement) {
|
||||
if (functionElement.getValueParameterList() == null) { // we won't be able to modify its signature
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
FunctionDescriptor functionDescriptor = (FunctionDescriptor) ResolutionUtils.resolveToDescriptor(functionElement);
|
||||
List<FunctionDescriptor> superFunctions = getPossibleSuperFunctionsDescriptors(functionDescriptor);
|
||||
final Map<String, FunctionDescriptor> possibleSignatures = Maps.newHashMap();
|
||||
for (FunctionDescriptor superFunction : superFunctions) {
|
||||
if (!superFunction.getKind().isReal()) continue;
|
||||
FunctionDescriptor signature = changeSignatureToMatch(functionDescriptor, superFunction);
|
||||
possibleSignatures.put(SIGNATURE_PREVIEW_RENDERER.render(signature), signature);
|
||||
}
|
||||
List<String> keys = new ArrayList<String>(possibleSignatures.keySet());
|
||||
Collections.sort(keys);
|
||||
return new ArrayList<FunctionDescriptor>(Collections2.transform(keys, new Function<String, FunctionDescriptor>() {
|
||||
@Override
|
||||
public FunctionDescriptor apply(String key) {
|
||||
return possibleSignatures.get(key);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes function's signature to match superFunction's signature. Returns new descriptor.
|
||||
*/
|
||||
private static FunctionDescriptor changeSignatureToMatch(FunctionDescriptor function, FunctionDescriptor superFunction) {
|
||||
List<ValueParameterDescriptor> superParameters = superFunction.getValueParameters();
|
||||
List<ValueParameterDescriptor> parameters = function.getValueParameters();
|
||||
List<ValueParameterDescriptor> newParameters = Lists.newArrayList(superParameters);
|
||||
|
||||
// Parameters in superFunction, which are matched in new function signature:
|
||||
BitSet matched = new BitSet(superParameters.size());
|
||||
// Parameters in this function, which are used in new function signature:
|
||||
BitSet used = new BitSet(superParameters.size());
|
||||
|
||||
matchParameters(MATCH_NAMES, superParameters, parameters, newParameters, matched, used);
|
||||
matchParameters(MATCH_TYPES, superParameters, parameters, newParameters, matched, used);
|
||||
|
||||
FunctionDescriptor newFunction = FunctionDescriptorUtil.replaceFunctionParameters(
|
||||
superFunction.copy(
|
||||
function.getContainingDeclaration(),
|
||||
Modality.OPEN,
|
||||
getVisibility(function, superFunction),
|
||||
CallableMemberDescriptor.Kind.DELEGATION,
|
||||
/* copyOverrides = */ true),
|
||||
newParameters);
|
||||
newFunction.addOverriddenDescriptor(superFunction);
|
||||
return newFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new visibility for 'function' modified to override 'superFunction'.
|
||||
*/
|
||||
private static Visibility getVisibility(FunctionDescriptor function, FunctionDescriptor superFunction) {
|
||||
ArrayDeque<CallableMemberDescriptor> descriptors =
|
||||
Queues.<CallableMemberDescriptor>newArrayDeque(Arrays.asList(superFunction, function));
|
||||
return VisibilityUtil.findMemberWithMaxVisibility(descriptors).getVisibility();
|
||||
}
|
||||
|
||||
/** Helper interface for matchParameters(..) method. */
|
||||
private interface ParameterChooser {
|
||||
/**
|
||||
* Checks if 'parameter' may be used to match 'superParameter'.
|
||||
* If so, returns (possibly modified) descriptor to be used as the new parameter.
|
||||
* If not, returns null.
|
||||
*/
|
||||
@Nullable
|
||||
ValueParameterDescriptor choose(@NotNull ValueParameterDescriptor parameter, @NotNull ValueParameterDescriptor superParameter);
|
||||
}
|
||||
|
||||
private static final ParameterChooser MATCH_NAMES = new ParameterChooser() {
|
||||
@Nullable
|
||||
@Override
|
||||
public ValueParameterDescriptor choose(
|
||||
@NotNull ValueParameterDescriptor parameter,
|
||||
@NotNull ValueParameterDescriptor superParameter
|
||||
) {
|
||||
return parameter.getName().equals(superParameter.getName()) ? superParameter : null;
|
||||
}
|
||||
};
|
||||
|
||||
private static final ParameterChooser MATCH_TYPES = new ParameterChooser() {
|
||||
@Nullable
|
||||
@Override
|
||||
public ValueParameterDescriptor choose(
|
||||
@NotNull ValueParameterDescriptor parameter,
|
||||
@NotNull ValueParameterDescriptor superParameter
|
||||
) {
|
||||
// TODO: support for generic functions
|
||||
if (JetTypeChecker.DEFAULT.equalTypes(parameter.getType(), superParameter.getType())) {
|
||||
return superParameter.copy(parameter.getContainingDeclaration(), parameter.getName());
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Match function's parameters with super function's parameters using parameterChooser.
|
||||
* Doesn't have to preserve ordering, parameter names or types.
|
||||
* @param superParameters - super function's parameters
|
||||
* @param parameters - function's parameters
|
||||
* @param newParameters - new parameters (may be modified by this function)
|
||||
* @param matched - true iff this parameter in super function is matched by some parameter in function (may be modified by this function)
|
||||
* @param used - true iff this parameter in function is used to match some parameter in super function (may be modified by this function)
|
||||
*/
|
||||
private static void matchParameters(
|
||||
@NotNull ParameterChooser parameterChooser,
|
||||
@NotNull List<ValueParameterDescriptor> superParameters,
|
||||
@NotNull List<ValueParameterDescriptor> parameters,
|
||||
@NotNull List<ValueParameterDescriptor> newParameters,
|
||||
@NotNull BitSet matched,
|
||||
@NotNull BitSet used
|
||||
) {
|
||||
for (ValueParameterDescriptor superParameter : superParameters) {
|
||||
if (!matched.get(superParameter.getIndex())) {
|
||||
for (ValueParameterDescriptor parameter : parameters) {
|
||||
ValueParameterDescriptor choice = parameterChooser.choose(parameter, superParameter);
|
||||
if (!used.get(parameter.getIndex()) && choice != null) {
|
||||
used.set(parameter.getIndex(), true);
|
||||
matched.set(superParameter.getIndex(), true);
|
||||
newParameters.set(superParameter.getIndex(), choice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all open functions in superclasses which have the same name as 'functionDescriptor' (but possibly
|
||||
* different parameters/return type).
|
||||
*/
|
||||
@NotNull
|
||||
private static List<FunctionDescriptor> getPossibleSuperFunctionsDescriptors(@NotNull FunctionDescriptor functionDescriptor) {
|
||||
DeclarationDescriptor containingDeclaration = functionDescriptor.getContainingDeclaration();
|
||||
List<FunctionDescriptor> superFunctions = Lists.newArrayList();
|
||||
if (!(containingDeclaration instanceof ClassDescriptor)) return superFunctions;
|
||||
ClassDescriptor classDescriptor = (ClassDescriptor) containingDeclaration;
|
||||
|
||||
Name name = functionDescriptor.getName();
|
||||
for (JetType type : TypeUtils.getAllSupertypes(classDescriptor.getDefaultType())) {
|
||||
JetScope scope = type.getMemberScope();
|
||||
for (FunctionDescriptor function : scope.getFunctions(name, NoLookupLocation.FROM_IDE)) {
|
||||
if (!function.getKind().isReal()) continue;
|
||||
if (function.getModality().isOverridable())
|
||||
superFunctions.add(function);
|
||||
}
|
||||
}
|
||||
return superFunctions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JetSingleIntentionActionFactory createFactory() {
|
||||
return new JetSingleIntentionActionFactory() {
|
||||
@Nullable
|
||||
@Override
|
||||
public IntentionAction createAction(Diagnostic diagnostic) {
|
||||
JetNamedFunction function = QuickFixUtil.getParentElementOfType(diagnostic, JetNamedFunction.class);
|
||||
return function == null ? null : new ChangeMemberFunctionSignatureFix(function);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showHint(@NotNull Editor editor) {
|
||||
if (possibleSignatures.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Project project = editor.getProject();
|
||||
if (project == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class MyAction {
|
||||
private final Project project;
|
||||
private final Editor editor;
|
||||
private final JetNamedFunction function;
|
||||
private final List<FunctionDescriptor> signatures;
|
||||
|
||||
public MyAction(
|
||||
@NotNull Project project,
|
||||
@NotNull Editor editor,
|
||||
@NotNull JetNamedFunction function,
|
||||
@NotNull List<FunctionDescriptor> signatures
|
||||
) {
|
||||
this.project = project;
|
||||
this.editor = editor;
|
||||
this.function = function;
|
||||
this.signatures = signatures;
|
||||
}
|
||||
|
||||
public boolean execute() {
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments();
|
||||
|
||||
if (!function.isValid() || signatures.isEmpty()) return false;
|
||||
|
||||
if (signatures.size() == 1 || !editor.getComponent().isShowing()) {
|
||||
changeSignature(signatures.get(0));
|
||||
}
|
||||
else {
|
||||
chooseSignatureAndChange();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private BaseListPopupStep getSignaturePopup() {
|
||||
return new BaseListPopupStep<FunctionDescriptor>(
|
||||
JetBundle.message("change.function.signature.chooser.title"), signatures) {
|
||||
@Override
|
||||
public boolean isAutoSelectionEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PopupStep onChosen(FunctionDescriptor selectedValue, boolean finalChoice) {
|
||||
if (finalChoice) {
|
||||
changeSignature(selectedValue);
|
||||
}
|
||||
return FINAL_CHOICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIconFor(FunctionDescriptor aValue) {
|
||||
return PlatformIcons.FUNCTION_ICON;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getTextFor(FunctionDescriptor aValue) {
|
||||
return SIGNATURE_PREVIEW_RENDERER.render(aValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void changeSignature(FunctionDescriptor patternDescriptor) {
|
||||
final String signatureString = SIGNATURE_SOURCE_RENDERER.render(patternDescriptor);
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments();
|
||||
|
||||
final JetPsiFactory psiFactory = JetPsiFactoryKt.JetPsiFactory(project);
|
||||
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
JetNamedFunction patternFunction = psiFactory.createFunction(signatureString);
|
||||
|
||||
JetTypeReference newTypeRef = function.setTypeReference(patternFunction.getTypeReference());
|
||||
if (newTypeRef != null) {
|
||||
ShortenReferences.DEFAULT.process(newTypeRef);
|
||||
}
|
||||
|
||||
JetParameterList newParameterList = (JetParameterList) function.getValueParameterList().replace(patternFunction.getValueParameterList());
|
||||
ShortenReferences.DEFAULT.process(newParameterList);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, JetBundle.message("change.function.signature.action"), null);
|
||||
}
|
||||
|
||||
private void chooseSignatureAndChange() {
|
||||
JBPopupFactory.getInstance().createListPopup(getSignaturePopup()).showInBestPositionFor(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.kotlin.idea.quickfix
|
||||
|
||||
import com.google.common.base.Function
|
||||
import com.google.common.collect.Collections2
|
||||
import com.google.common.collect.Lists
|
||||
import com.google.common.collect.Maps
|
||||
import com.google.common.collect.Queues
|
||||
import com.intellij.codeInsight.hint.HintManager
|
||||
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.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.ui.popup.PopupStep
|
||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.util.PlatformIcons
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.JetBundle
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetNamedFunction
|
||||
import org.jetbrains.kotlin.psi.JetParameterList
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
|
||||
import org.jetbrains.kotlin.renderer.NameShortness
|
||||
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil
|
||||
import org.jetbrains.kotlin.resolve.VisibilityUtil
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.checker.JetTypeChecker
|
||||
import java.util.*
|
||||
import javax.swing.Icon
|
||||
|
||||
/**
|
||||
* Fix that changes member function's signature to match one of super functions' signatures.
|
||||
*/
|
||||
class ChangeMemberFunctionSignatureFix(element: JetNamedFunction) : JetHintAction<JetNamedFunction>(element) {
|
||||
|
||||
companion object {
|
||||
private val SIGNATURE_SOURCE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE.withOptions {
|
||||
renderDefaultValues = false
|
||||
}
|
||||
|
||||
private val SIGNATURE_PREVIEW_RENDERER = DescriptorRenderer.withOptions {
|
||||
typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES
|
||||
withDefinedIn = false
|
||||
modifiers = emptySet<DescriptorRendererModifier>()
|
||||
nameShortness = NameShortness.SHORT
|
||||
unitReturnType = false
|
||||
renderDefaultValues = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes all the signatures a 'functionElement' could be changed to in order to remove NOTHING_TO_OVERRIDE error.
|
||||
*/
|
||||
private fun computePossibleSignatures(functionElement: JetNamedFunction): List<FunctionDescriptor> {
|
||||
if (functionElement.valueParameterList == null) {
|
||||
// we won't be able to modify its signature
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val functionDescriptor = functionElement.resolveToDescriptor() as FunctionDescriptor
|
||||
val superFunctions = getPossibleSuperFunctionsDescriptors(functionDescriptor)
|
||||
val possibleSignatures = Maps.newHashMap<String, FunctionDescriptor>()
|
||||
for (superFunction in superFunctions) {
|
||||
if (!superFunction.kind.isReal) continue
|
||||
val signature = changeSignatureToMatch(functionDescriptor, superFunction)
|
||||
possibleSignatures.put(SIGNATURE_PREVIEW_RENDERER.render(signature), signature)
|
||||
}
|
||||
val keys = ArrayList(possibleSignatures.keys)
|
||||
Collections.sort(keys)
|
||||
return ArrayList(Collections2.transform(keys, object : Function<String, FunctionDescriptor> {
|
||||
override fun apply(key: String?): FunctionDescriptor? {
|
||||
return possibleSignatures.get(key)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes function's signature to match superFunction's signature. Returns new descriptor.
|
||||
*/
|
||||
private fun changeSignatureToMatch(function: FunctionDescriptor, superFunction: FunctionDescriptor): FunctionDescriptor {
|
||||
val superParameters = superFunction.valueParameters
|
||||
val parameters = function.valueParameters
|
||||
val newParameters = Lists.newArrayList(superParameters)
|
||||
|
||||
// Parameters in superFunction, which are matched in new function signature:
|
||||
val matched = BitSet(superParameters.size)
|
||||
// Parameters in this function, which are used in new function signature:
|
||||
val used = BitSet(superParameters.size)
|
||||
|
||||
matchParameters(MATCH_NAMES, superParameters, parameters, newParameters, matched, used)
|
||||
matchParameters(MATCH_TYPES, superParameters, parameters, newParameters, matched, used)
|
||||
|
||||
val newFunction = FunctionDescriptorUtil.replaceFunctionParameters(
|
||||
superFunction.copy(
|
||||
function.containingDeclaration,
|
||||
Modality.OPEN,
|
||||
getVisibility(function, superFunction),
|
||||
CallableMemberDescriptor.Kind.DELEGATION,
|
||||
/* copyOverrides = */ true),
|
||||
newParameters)
|
||||
newFunction.addOverriddenDescriptor(superFunction)
|
||||
return newFunction
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new visibility for 'function' modified to override 'superFunction'.
|
||||
*/
|
||||
private fun getVisibility(function: FunctionDescriptor, superFunction: FunctionDescriptor): Visibility {
|
||||
val descriptors = Queues.newArrayDeque<CallableMemberDescriptor>(Arrays.asList(superFunction, function))
|
||||
return VisibilityUtil.findMemberWithMaxVisibility(descriptors).visibility
|
||||
}
|
||||
|
||||
private val MATCH_NAMES = object : ParameterChooser {
|
||||
override fun choose(
|
||||
parameter: ValueParameterDescriptor,
|
||||
superParameter: ValueParameterDescriptor): ValueParameterDescriptor? {
|
||||
return if (parameter.name == superParameter.name) superParameter else null
|
||||
}
|
||||
}
|
||||
|
||||
private val MATCH_TYPES = object : ParameterChooser {
|
||||
override fun choose(
|
||||
parameter: ValueParameterDescriptor,
|
||||
superParameter: ValueParameterDescriptor): ValueParameterDescriptor? {
|
||||
// TODO: support for generic functions
|
||||
if (JetTypeChecker.DEFAULT.equalTypes(parameter.type, superParameter.type)) {
|
||||
return superParameter.copy(parameter.containingDeclaration, parameter.name)
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match function's parameters with super function's parameters using parameterChooser.
|
||||
* Doesn't have to preserve ordering, parameter names or types.
|
||||
* @param superParameters - super function's parameters
|
||||
* *
|
||||
* @param parameters - function's parameters
|
||||
* *
|
||||
* @param newParameters - new parameters (may be modified by this function)
|
||||
* *
|
||||
* @param matched - true iff this parameter in super function is matched by some parameter in function (may be modified by this function)
|
||||
* *
|
||||
* @param used - true iff this parameter in function is used to match some parameter in super function (may be modified by this function)
|
||||
*/
|
||||
private fun matchParameters(
|
||||
parameterChooser: ParameterChooser,
|
||||
superParameters: List<ValueParameterDescriptor>,
|
||||
parameters: List<ValueParameterDescriptor>,
|
||||
newParameters: MutableList<ValueParameterDescriptor>,
|
||||
matched: BitSet,
|
||||
used: BitSet) {
|
||||
for (superParameter in superParameters) {
|
||||
if (!matched.get(superParameter.index)) {
|
||||
for (parameter in parameters) {
|
||||
val choice = parameterChooser.choose(parameter, superParameter)
|
||||
if (!used.get(parameter.index) && choice != null) {
|
||||
used.set(parameter.index, true)
|
||||
matched.set(superParameter.index, true)
|
||||
newParameters.set(superParameter.index, choice)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all open functions in superclasses which have the same name as 'functionDescriptor' (but possibly
|
||||
* different parameters/return type).
|
||||
*/
|
||||
private fun getPossibleSuperFunctionsDescriptors(functionDescriptor: FunctionDescriptor): List<FunctionDescriptor> {
|
||||
val containingDeclaration = functionDescriptor.containingDeclaration
|
||||
val superFunctions = Lists.newArrayList<FunctionDescriptor>()
|
||||
if (containingDeclaration !is ClassDescriptor) return superFunctions
|
||||
|
||||
val name = functionDescriptor.name
|
||||
for (type in TypeUtils.getAllSupertypes(containingDeclaration.defaultType)) {
|
||||
val scope = type.memberScope
|
||||
for (function in scope.getFunctions(name, NoLookupLocation.FROM_IDE)) {
|
||||
if (!function.kind.isReal) continue
|
||||
if (function.modality.isOverridable)
|
||||
superFunctions.add(function)
|
||||
}
|
||||
}
|
||||
return superFunctions
|
||||
}
|
||||
|
||||
fun createFactory(): JetSingleIntentionActionFactory {
|
||||
return object : JetSingleIntentionActionFactory() {
|
||||
public override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val function = QuickFixUtil.getParentElementOfType(diagnostic, JetNamedFunction::class.java)
|
||||
return if (function == null) null else ChangeMemberFunctionSignatureFix(function)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val possibleSignatures = computePossibleSignatures(element)
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
return super.isAvailable(project, editor, file) && !possibleSignatures.isEmpty()
|
||||
}
|
||||
|
||||
override fun getText(): String {
|
||||
if (possibleSignatures.size == 1) {
|
||||
return JetBundle.message("change.function.signature.action.single", SIGNATURE_PREVIEW_RENDERER.render(possibleSignatures.get(0)))
|
||||
}
|
||||
else {
|
||||
return JetBundle.message("change.function.signature.action.multiple")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFamilyName(): String {
|
||||
return JetBundle.message("change.function.signature.family")
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: JetFile) {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction(object : Runnable {
|
||||
override fun run() {
|
||||
MyAction(project, editor, element, possibleSignatures).execute()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** Helper interface for matchParameters(..) method. */
|
||||
private interface ParameterChooser {
|
||||
/**
|
||||
* Checks if 'parameter' may be used to match 'superParameter'.
|
||||
* If so, returns (possibly modified) descriptor to be used as the new parameter.
|
||||
* If not, returns null.
|
||||
*/
|
||||
fun choose(parameter: ValueParameterDescriptor, superParameter: ValueParameterDescriptor): ValueParameterDescriptor?
|
||||
}
|
||||
|
||||
override fun showHint(editor: Editor): Boolean {
|
||||
return possibleSignatures.isNotEmpty() && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)
|
||||
}
|
||||
|
||||
private class MyAction(
|
||||
private val project: Project,
|
||||
private val editor: Editor?,
|
||||
private val function: JetNamedFunction,
|
||||
private val signatures: List<FunctionDescriptor>) {
|
||||
|
||||
fun execute(): Boolean {
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
|
||||
if (!function.isValid || signatures.isEmpty()) return false
|
||||
|
||||
if (signatures.size == 1 || editor == null || !editor.component.isShowing) {
|
||||
changeSignature(signatures.get(0))
|
||||
}
|
||||
else {
|
||||
chooseSignatureAndChange()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private val signaturePopup: BaseListPopupStep<FunctionDescriptor>
|
||||
get() {
|
||||
return object : BaseListPopupStep<FunctionDescriptor>(JetBundle.message("change.function.signature.chooser.title"), signatures) {
|
||||
override fun isAutoSelectionEnabled(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onChosen(selectedValue: FunctionDescriptor, finalChoice: Boolean): PopupStep<Any>? {
|
||||
if (finalChoice) {
|
||||
changeSignature(selectedValue)
|
||||
}
|
||||
return PopupStep.FINAL_CHOICE
|
||||
}
|
||||
|
||||
override fun getIconFor(aValue: FunctionDescriptor): Icon? {
|
||||
return PlatformIcons.FUNCTION_ICON
|
||||
}
|
||||
|
||||
override fun getTextFor(aValue: FunctionDescriptor): String {
|
||||
return SIGNATURE_PREVIEW_RENDERER.render(aValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeSignature(patternDescriptor: FunctionDescriptor) {
|
||||
val signatureString = SIGNATURE_SOURCE_RENDERER.render(patternDescriptor)
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
|
||||
val psiFactory = JetPsiFactory(project)
|
||||
project.executeWriteCommand(JetBundle.message("change.function.signature.action")) {
|
||||
val patternFunction = psiFactory.createFunction(signatureString)
|
||||
|
||||
val newTypeRef = function.setTypeReference(patternFunction.typeReference)
|
||||
if (newTypeRef != null) {
|
||||
ShortenReferences.DEFAULT.process(newTypeRef)
|
||||
}
|
||||
|
||||
val newParameterList = function.valueParameterList!!.replace(patternFunction.valueParameterList!!) as JetParameterList
|
||||
ShortenReferences.DEFAULT.process(newParameterList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseSignatureAndChange() {
|
||||
JBPopupFactory.getInstance().createListPopup(signaturePopup).showInBestPositionFor(editor!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user