From 16ceb446a3eb04f7ddf454d4d1bfa76348cac1fe Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Jan 2012 21:05:30 +0400 Subject: [PATCH] Fixed completion for method calls. Initial support of Parameter Info (Ctrl+P) feature (including named parameters reordering). Todo list: 1. Constructors are not working now (even primary constructor). 2. Tests. --- .../jetbrains/jet/compiler/TipsManager.java | 21 +- .../jet/lang/psi/JetSimpleNameExpression.java | 29 ++ .../lang/resolve/JetVisibilityChecker.java | 2 +- .../jet/lang/resolve/calls/CallResolver.java | 1 + .../org/jetbrains/jet/lang/types/JetType.java | 2 + idea/src/META-INF/plugin.xml | 2 + .../JetFunctionParameterInfoHandler.java | 328 ++++++++++++++++++ 7 files changed, 369 insertions(+), 16 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/parameterInfo/JetFunctionParameterInfoHandler.java diff --git a/compiler/backend/src/org/jetbrains/jet/compiler/TipsManager.java b/compiler/backend/src/org/jetbrains/jet/compiler/TipsManager.java index 36d4e7eeb07..bfde3a031a5 100644 --- a/compiler/backend/src/org/jetbrains/jet/compiler/TipsManager.java +++ b/compiler/backend/src/org/jetbrains/jet/compiler/TipsManager.java @@ -10,10 +10,7 @@ import org.jetbrains.jet.lang.descriptors.CallableDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor; import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor; -import org.jetbrains.jet.lang.psi.JetExpression; -import org.jetbrains.jet.lang.psi.JetImportDirective; -import org.jetbrains.jet.lang.psi.JetQualifiedExpression; -import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; +import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintResolutionListener; import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystem; @@ -43,13 +40,9 @@ public final class TipsManager { @NotNull public static Collection getReferenceVariants(JetSimpleNameExpression expression, BindingContext context) { - PsiElement parent = expression.getParent(); - - if (parent instanceof JetQualifiedExpression && !(parent.getParent() instanceof JetImportDirective)) { + JetExpression receiverExpression = expression.getReceiverExpression(); + if (receiverExpression != null) { // Process as call expression - JetQualifiedExpression qualifiedExpression = (JetQualifiedExpression) parent; - JetExpression receiverExpression = qualifiedExpression.getReceiverExpression(); - final JetScope resolutionScope = context.get(BindingContext.RESOLUTION_SCOPE, receiverExpression); final JetType expressionType = context.get(BindingContext.EXPRESSION_TYPE, receiverExpression); @@ -58,14 +51,12 @@ public final class TipsManager { expressionType.getMemberScope().getAllDescriptors(), resolutionScope, new ExpressionReceiver(receiverExpression, expressionType)); } - } - else { + } else { JetScope resolutionScope = context.get(BindingContext.RESOLUTION_SCOPE, expression); if (resolutionScope != null) { - if (parent instanceof JetImportDirective) { + if (expression.getParent() instanceof JetImportDirective) { return excludeNonPackageDescriptors(resolutionScope.getAllDescriptors()); - } - else { + } else { return excludeNotCallableExtensions(resolutionScope.getAllDescriptors(), resolutionScope); } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java index 8b2d824ad3c..fc2d71a0772 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java @@ -23,6 +23,35 @@ public class JetSimpleNameExpression extends JetReferenceExpression { super(node); } + /** + * null if it's not a code expression + * @return receiver expression + */ + @Nullable + public JetExpression getReceiverExpression() { + PsiElement parent = getParent(); + if (parent instanceof JetQualifiedExpression && !isImportDirectiveExpression()) { + JetQualifiedExpression qualifiedExpression = (JetQualifiedExpression) parent; + return qualifiedExpression.getReceiverExpression(); + } else if (parent instanceof JetCallExpression) { + //This is in case `a().b()` + JetCallExpression callExpression = (JetCallExpression) parent; + parent = callExpression.getParent(); + if (parent instanceof JetQualifiedExpression) { + JetQualifiedExpression qualifiedExpression = (JetQualifiedExpression) parent; + return qualifiedExpression.getReceiverExpression(); + } + } + return null; + } + + public boolean isImportDirectiveExpression() { + PsiElement parent = getParent(); + if (parent == null) return false; + else return parent instanceof JetImportDirective || + parent.getParent() instanceof JetImportDirective; + } + @Nullable @IfNotParsed public String getReferencedName() { PsiElement referencedNameElement = getReferencedNameElement(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/JetVisibilityChecker.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/JetVisibilityChecker.java index 92ebed8e995..6c92cadbc85 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/JetVisibilityChecker.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/JetVisibilityChecker.java @@ -12,7 +12,7 @@ public class JetVisibilityChecker { * @param subject the descriptor whose visibility is being checked * @return true iff subject is visible locationOwner */ - public boolean isVisible(@NotNull DeclarationDescriptor locationOwner, @NotNull DeclarationDescriptor subject) { + public static boolean isVisible(@NotNull DeclarationDescriptor locationOwner, @NotNull DeclarationDescriptor subject) { // TODO : stub implementation return true; } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java index 11d84cc5851..deb76530515 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java @@ -241,6 +241,7 @@ public class CallResolver { ResolutionDebugInfo.Data debugInfo = ResolutionDebugInfo.create(); trace.record(ResolutionDebugInfo.RESOLUTION_DEBUG_INFO, call.getCallElement(), debugInfo); + trace.record(RESOLUTION_SCOPE, call.getCalleeExpression(), scope); debugInfo.set(ResolutionDebugInfo.TASKS, prioritizedTasks); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/JetType.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/JetType.java index 0fbef22c2d8..c6e27f03a04 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/JetType.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/JetType.java @@ -3,11 +3,13 @@ package org.jetbrains.jet.lang.types; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.annotations.Annotated; import org.jetbrains.jet.lang.resolve.scopes.JetScope; +import org.jetbrains.jet.lang.types.checker.JetTypeChecker; import java.util.List; /** * @author abreslav + * @see JetTypeChecker#isSubtypeOf(JetType, JetType) */ public interface JetType extends Annotated { @NotNull TypeConstructor getConstructor(); diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 826947e6808..7fc1597c86e 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -51,6 +51,8 @@ + + diff --git a/idea/src/org/jetbrains/jet/plugin/parameterInfo/JetFunctionParameterInfoHandler.java b/idea/src/org/jetbrains/jet/plugin/parameterInfo/JetFunctionParameterInfoHandler.java new file mode 100644 index 00000000000..cf4816a98b5 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/parameterInfo/JetFunctionParameterInfoHandler.java @@ -0,0 +1,328 @@ +package org.jetbrains.jet.plugin.parameterInfo; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.lang.ASTNode; +import com.intellij.lang.parameterInfo.*; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.compiler.TipsManager; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.JetVisibilityChecker; +import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade; +import org.jetbrains.jet.lang.resolve.scopes.JetScope; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.checker.JetTypeChecker; +import org.jetbrains.jet.lexer.JetTokens; +import org.jetbrains.jet.resolve.DescriptorRenderer; + +import java.awt.*; +import java.util.*; +import java.util.List; + +/** + * User: Alefas + * Date: 17.01.12 + */ +public class JetFunctionParameterInfoHandler implements + ParameterInfoHandlerWithTabActionSupport { + @NotNull + @Override + public JetValueArgument[] getActualParameters(@NotNull JetValueArgumentList arguments) { + List argumentList = arguments.getArguments(); + return argumentList.toArray(new JetValueArgument[argumentList.size()]); + } + + @NotNull + @Override + public IElementType getActualParameterDelimiterType() { + return JetTokens.COMMA; + } + + @NotNull + @Override + public IElementType getActualParametersRBraceType() { + return JetTokens.RBRACE; + } + + @NotNull + @Override + public Set getArgumentListAllowedParentClasses() { + return Collections.singleton((Class) JetCallExpression.class); + } + + @NotNull + @Override + public Set getArgListStopSearchClasses() { + return Collections.singleton(JetFunction.class); + } + + @NotNull + @Override + public Class getArgumentListClass() { + return JetValueArgumentList.class; + } + + @Override + public boolean couldShowInLookup() { + return true; + } + + @Override + public Object[] getParametersForLookup(LookupElement item, ParameterInfoContext context) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; //todo: + } + + @Override + public Object[] getParametersForDocumentation(Object p, ParameterInfoContext context) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; //todo: + } + + @Override + public JetValueArgumentList findElementForParameterInfo(CreateParameterInfoContext context) { + return findCall(context); + } + + @Override + public void showParameterInfo(@NotNull JetValueArgumentList element, CreateParameterInfoContext context) { + context.showHint(element, element.getTextRange().getStartOffset(), this); + } + + @Override + public JetValueArgumentList findElementForUpdatingParameterInfo(UpdateParameterInfoContext context) { + return findCallAndUpdateContext(context); + } + + @Override + public void updateParameterInfo(@NotNull JetValueArgumentList argumentList, UpdateParameterInfoContext context) { + if (context.getParameterOwner() != argumentList) context.removeHint(); + int offset = context.getOffset(); + ASTNode child = argumentList.getNode().getFirstChildNode(); + int i = 0; + while (child != null && child.getStartOffset() < offset) { + if (child.getElementType() == JetTokens.COMMA) ++i; + child = child.getTreeNext(); + } + context.setCurrentParameter(i); + } + + @Override + public String getParameterCloseChars() { + return ParameterInfoUtils.DEFAULT_PARAMETER_CLOSE_CHARS; + } + + @Override + public boolean tracksParameterIndex() { + return true; + } + + private static String renderParameter(ValueParameterDescriptor descriptor, boolean named) { + StringBuilder builder = new StringBuilder(); + if (named) builder.append("["); + if (descriptor.getVarargElementType() != null) { + builder.append("vararg "); + } + builder.append(descriptor.getName()).append(": "). + append(DescriptorRenderer.TEXT.renderType(getActualParameterType(descriptor))); + if (descriptor.hasDefaultValue()) { + builder.append(" = {...}"); + } + if (named) builder.append("]"); + return builder.toString(); + } + + private static JetType getActualParameterType(ValueParameterDescriptor descriptor) { + JetType paramType = descriptor.getOutType(); + if (descriptor.getVarargElementType() != null) paramType = descriptor.getVarargElementType(); + return paramType; + } + + @Override + public void updateUI(Object descriptor, ParameterInfoUIContext context) { + //todo: when we will have ability to pass Array as vararg, implement such feature here too + //todo: check for constructors + //todo: TESTS!!! + if (context == null || context.getParameterOwner() == null || !context.getParameterOwner().isValid()) { + return; + } + PsiElement parameterOwner = context.getParameterOwner(); + if (parameterOwner instanceof JetValueArgumentList) { + JetValueArgumentList argumentList = (JetValueArgumentList) parameterOwner; + if (descriptor instanceof FunctionDescriptor) { + JetFile file = (JetFile) argumentList.getContainingFile(); + BindingContext bindingContext = + AnalyzerFacade.analyzeFileWithCache(file, AnalyzerFacade.SINGLE_DECLARATION_PROVIDER); + FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor; + StringBuilder builder = new StringBuilder(); + List valueParameters = functionDescriptor.getValueParameters(); + List valueArguments = argumentList.getArguments(); + int currentParameterIndex = context.getCurrentParameterIndex(); + int boldStartOffset = -1; + int boldEndOffset = -1; + boolean isGrey = false; + boolean isDeprecated = false; //todo: add deprecation check + Color color = context.getDefaultParameterColor(); //todo: choose descriptors to highlight green + boolean[] usedIndexes = new boolean[valueParameters.size()]; + boolean namedMode = false; + Arrays.fill(usedIndexes, false); + if ((currentParameterIndex >= valueParameters.size() && (valueParameters.size() > 0 || + currentParameterIndex > 0)) && + (valueParameters.size() == 0 || valueParameters.get(valueParameters.size() - 1).getVarargElementType() == null)) { + isGrey = true; + } + if (valueParameters.size() == 0) builder.append(CodeInsightBundle.message("parameter.info.no.parameters")); + for (int i = 0; i < valueParameters.size(); ++i) { + if (i != 0) builder.append(", "); + boolean highlightParameter = + i == currentParameterIndex || (!namedMode && i < currentParameterIndex && + valueParameters.get(valueParameters.size() - 1). + getVarargElementType() != null); + if (highlightParameter) boldStartOffset = builder.length(); + if (!namedMode) { + if (valueArguments.size() > i) { + JetValueArgument argument = valueArguments.get(i); + if (argument.isNamed()) { + namedMode = true; + } else { + ValueParameterDescriptor param = valueParameters.get(i); + builder.append(renderParameter(param, false)); + if (i < currentParameterIndex) { + if (argument.getArgumentExpression() != null) { + //check type + JetType paramType = getActualParameterType(param); + JetType exprType = bindingContext.get(BindingContext.EXPRESSION_TYPE, argument.getArgumentExpression()); + if (exprType != null && !JetTypeChecker.INSTANCE.isSubtypeOf(exprType, paramType)) isGrey = true; + } + else isGrey = true; + } + usedIndexes[i] = true; + } + } else { + ValueParameterDescriptor param = valueParameters.get(i); + builder.append(renderParameter(param, false)); + } + } + if (namedMode) { + boolean takeAnyArgument = true; + if (valueArguments.size() > i) { + JetValueArgument argument = valueArguments.get(i); + if (argument.isNamed()) { + for (int j = 0; j < valueParameters.size(); ++j) { + JetSimpleNameExpression referenceExpression = argument.getArgumentName().getReferenceExpression(); + ValueParameterDescriptor param = valueParameters.get(j); + if (referenceExpression != null && !usedIndexes[j] && + param.getName().equals(referenceExpression.getReferencedName())) { + takeAnyArgument = false; + usedIndexes[j] = true; + builder.append(renderParameter(param, true)); + if (i < currentParameterIndex) { + if (argument.getArgumentExpression() != null) { + //check type + JetType paramType = getActualParameterType(param); + JetType exprType = bindingContext.get(BindingContext.EXPRESSION_TYPE, argument.getArgumentExpression()); + if (exprType != null && !JetTypeChecker.INSTANCE.isSubtypeOf(exprType, paramType)) isGrey = true; + } + else isGrey = true; + } + break; + } + } + } + } + + if (takeAnyArgument) { + if (i < currentParameterIndex) isGrey = true; + + for (int j = 0; j < valueParameters.size(); ++j) { + ValueParameterDescriptor param = valueParameters.get(j); + if (!usedIndexes[j]) { + usedIndexes[j] = true; + builder.append(renderParameter(param, true)); + break; + } + } + } + } + if (highlightParameter) boldEndOffset = builder.length(); + } + if (builder.toString() == "") context.setUIComponentEnabled(false); + else context.setupUIComponentPresentation(builder.toString(), boldStartOffset, boldEndOffset, isGrey, + isDeprecated, false, color); + } else context.setUIComponentEnabled(false); + } + } + + private static JetValueArgumentList findCall(CreateParameterInfoContext context) { + PsiFile file = context.getFile(); + if (!(file instanceof JetFile)) return null; + PsiElement element = file.findElementAt(context.getOffset()); + while (element != null && !(element instanceof JetValueArgumentList)) { + element = element.getParent(); + } + if (element == null) return null; + JetValueArgumentList argumentList = (JetValueArgumentList) element; + JetCallExpression callExpression; + if (element.getParent() instanceof JetCallExpression) { + callExpression = (JetCallExpression) element.getParent(); + } else return null; + BindingContext bindingContext = AnalyzerFacade.analyzeFileWithCache((JetFile) file, + AnalyzerFacade.SINGLE_DECLARATION_PROVIDER); + JetExpression calleeExpression = callExpression.getCalleeExpression(); + if (calleeExpression == null) return null; + if (calleeExpression instanceof JetSimpleNameExpression) { + JetSimpleNameExpression refExpression = (JetSimpleNameExpression) calleeExpression; + JetScope scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, refExpression); + DeclarationDescriptor placeDescriptor = null; + if (scope != null) { + placeDescriptor = scope.getContainingDeclaration(); + } + Collection variants = TipsManager.getReferenceVariants(refExpression, bindingContext); + String refName = refExpression.getReferencedName(); + PsiReference[] references = refExpression.getReferences(); + if (references.length == 0) return null; + ArrayList itemsToShow = new ArrayList(); + for (DeclarationDescriptor variant : variants) { + if (variant instanceof FunctionDescriptor) { + FunctionDescriptor functionDescriptor = (FunctionDescriptor) variant; + if (functionDescriptor.getName().equals(refName)) { + //todo: renamed functions? + if (placeDescriptor != null && !JetVisibilityChecker.isVisible(placeDescriptor, functionDescriptor)) continue; + itemsToShow.add(functionDescriptor); + } + } + } + context.setItemsToShow(ArrayUtil.toObjectArray(itemsToShow)); + return argumentList; + } else { + return null; //todo: this expression? + } + } + + private static JetValueArgumentList findCallAndUpdateContext(UpdateParameterInfoContext context) { + PsiFile file = context.getFile(); + PsiElement element = file.findElementAt(context.getOffset()); + if (element == null) return null; + PsiElement parent = element.getParent(); + while (parent != null && !(parent instanceof JetValueArgumentList)) { + element = element.getParent(); + parent = parent.getParent(); + } + if (parent == null) return null; + JetValueArgumentList argumentList = (JetValueArgumentList) parent; + if (element instanceof JetValueArgument) { + JetValueArgument arg = (JetValueArgument) element; + int i = argumentList.getArguments().indexOf(arg); + context.setCurrentParameter(i); + context.setHighlightedParameter(arg); + } + return argumentList; + } +}