From 182009ab61eb91f5aaee6ef430f789da75b326bb Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Mon, 12 Dec 2011 12:12:45 +0400 Subject: [PATCH] KT-768 Code completion for extension functions --- .../lang/resolve/java/JavaPackageScope.java | 2 - .../lang/descriptors/CallableDescriptor.java | 2 + .../descriptors/DeclarationDescriptor.java | 1 + .../lang/resolve/AbstractScopeAdapter.java | 4 +- .../jet/lang/resolve/calls/CallResolver.java | 11 ++- .../lang/resolve/calls/ResolutionTask.java | 2 + .../lang/resolve/calls/TaskPrioritizer.java | 15 ++-- .../ValueArgumentsToParametersMapper.java | 17 +--- .../ConstraintResolutionListener.java | 22 +++++ .../jet/lang/resolve/scopes/JetScope.java | 5 ++ .../lang/resolve/scopes/JetScopeUtils.java | 54 ++++++++++++ .../expressions/ExpressionTypingContext.java | 5 +- .../references/JetSimpleNameReference.java | 87 +++++++++++++++++-- .../extensions/ExtensionInExtendedClass.kt | 12 +++ .../ExtensionInExtendedClassThis.kt | 10 +++ .../basic/extensions/ExtensionInExtension.kt | 11 +++ .../extensions/ExtensionInExtensionThis.kt | 11 +++ .../basic/extensions/InvalidTypeParameters.kt | 17 ++++ .../basic/extensions/IrrelevantExtension.kt | 11 +++ .../basic/extensions/JavaTypeExtension.kt | 10 +++ .../extensions/KotlinGenericTypeExtension.kt | 12 +++ .../basic/extensions/KotlinTypeExtension.kt | 12 +++ .../completion/ExtensionsCompletionTest.java | 43 +++++++++ .../completion/JetBasicCompletionTest.java | 26 +----- .../jet/completion/JetCompletionTestBase.java | 27 ++++++ .../completion/KeywordsCompletionTest.java | 27 +----- 26 files changed, 374 insertions(+), 82 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeUtils.java create mode 100644 idea/testData/completion/basic/extensions/ExtensionInExtendedClass.kt create mode 100644 idea/testData/completion/basic/extensions/ExtensionInExtendedClassThis.kt create mode 100644 idea/testData/completion/basic/extensions/ExtensionInExtension.kt create mode 100644 idea/testData/completion/basic/extensions/ExtensionInExtensionThis.kt create mode 100644 idea/testData/completion/basic/extensions/InvalidTypeParameters.kt create mode 100644 idea/testData/completion/basic/extensions/IrrelevantExtension.kt create mode 100644 idea/testData/completion/basic/extensions/JavaTypeExtension.kt create mode 100644 idea/testData/completion/basic/extensions/KotlinGenericTypeExtension.kt create mode 100644 idea/testData/completion/basic/extensions/KotlinTypeExtension.kt create mode 100644 idea/tests/org/jetbrains/jet/completion/ExtensionsCompletionTest.java diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaPackageScope.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaPackageScope.java index 0f28be38590..94240f21b34 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaPackageScope.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaPackageScope.java @@ -104,6 +104,4 @@ public class JavaPackageScope extends JetScopeImpl { private String getQualifiedName(String name) { return (packageFQN.isEmpty() ? "" : packageFQN + ".") + name; } - - } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/CallableDescriptor.java b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/CallableDescriptor.java index 752d0da55d3..1b8e6b06abc 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/CallableDescriptor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/CallableDescriptor.java @@ -1,6 +1,7 @@ package org.jetbrains.jet.lang.descriptors; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.TypeSubstitutor; @@ -24,6 +25,7 @@ public interface CallableDescriptor extends DeclarationDescriptor { /** * Method may return null for not yet fully initialized object or if error occurred. */ + @Nullable JetType getReturnType(); @NotNull diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/DeclarationDescriptor.java b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/DeclarationDescriptor.java index fcf17af68d9..e05dd66a7f5 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/DeclarationDescriptor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/DeclarationDescriptor.java @@ -21,6 +21,7 @@ public interface DeclarationDescriptor extends Annotated, Named { @Nullable DeclarationDescriptor getContainingDeclaration(); + @Nullable DeclarationDescriptor substitute(TypeSubstitutor substitutor); R accept(DeclarationDescriptorVisitor visitor, D data); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java index fec39dc6493..974d490667a 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AbstractScopeAdapter.java @@ -2,14 +2,16 @@ package org.jetbrains.jet.lang.resolve; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.*; -import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.lang.resolve.scopes.JetScope; +import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import java.util.Collection; import java.util.List; import java.util.Set; /** + * Introduces a simple wrapper for internal scope. + * * @author abreslav */ public abstract class AbstractScopeAdapter implements JetScope { 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 e611247ebe7..464445d0009 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 @@ -83,7 +83,7 @@ public class CallResolver { FunctionDescriptor functionDescriptor = resolveSimpleCallToFunctionDescriptor(trace, scope, call, expectedType); return functionDescriptor == null ? null : functionDescriptor.getReturnType(); } - + @Nullable public ResolvedCall resolveCallWithGivenName( @NotNull BindingTrace trace, @@ -92,7 +92,8 @@ public class CallResolver { @NotNull final JetReferenceExpression functionReference, @NotNull String name, @NotNull JetType expectedType) { - List> tasks = TaskPrioritizers.FUNCTION_TASK_PRIORITIZER.computePrioritizedTasks(scope, call, name, trace.getBindingContext(), dataFlowInfo); + List> tasks = TaskPrioritizers.FUNCTION_TASK_PRIORITIZER.computePrioritizedTasks( + scope, call, name, trace.getBindingContext(), dataFlowInfo); return doResolveCall(trace, scope, call, expectedType, tasks, functionReference); } @@ -403,7 +404,11 @@ public class CallResolver { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @NotNull - private OverloadResolutionResults performResolution(@NotNull BindingTrace trace, @NotNull JetScope scope, @NotNull JetType expectedType, @NotNull ResolutionTask task, @NotNull TracingStrategy tracing) { + private OverloadResolutionResults performResolution( + @NotNull BindingTrace trace, + @NotNull JetScope scope, @NotNull JetType expectedType, + @NotNull ResolutionTask task, @NotNull TracingStrategy tracing + ) { for (ResolvedCallImpl candidateCall : task.getCandidates()) { D candidate = candidateCall.getCandidateDescriptor(); TemporaryBindingTrace temporaryTrace = TemporaryBindingTrace.create(trace); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ResolutionTask.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ResolutionTask.java index ecde91cc4ed..dc2f978517c 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ResolutionTask.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ResolutionTask.java @@ -9,6 +9,8 @@ import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo; import java.util.Collection; /** + * Stores candidates for call resolution. + * * @author abreslav */ /*package*/ class ResolutionTask { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TaskPrioritizer.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TaskPrioritizer.java index 661dc9913ea..72137c2d6ca 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TaskPrioritizer.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TaskPrioritizer.java @@ -17,8 +17,8 @@ import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.lang.types.JetType; -import org.jetbrains.jet.lang.types.checker.JetTypeChecker; import org.jetbrains.jet.lang.types.NamespaceType; +import org.jetbrains.jet.lang.types.checker.JetTypeChecker; import java.util.Collection; import java.util.Collections; @@ -32,8 +32,11 @@ import static org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor /*package*/ abstract class TaskPrioritizer { public static void splitLexicallyLocalDescriptors( - Collection> allDescriptors, DeclarationDescriptor containerOfTheCurrentLocality, Collection> local, Collection> nonlocal) { - + @NotNull Collection> allDescriptors, + @NotNull DeclarationDescriptor containerOfTheCurrentLocality, + @NotNull Collection> local, + @NotNull Collection> nonlocal + ) { for (ResolvedCallImpl resolvedCall : allDescriptors) { if (DescriptorUtils.isLocal(containerOfTheCurrentLocality, resolvedCall.getCandidateDescriptor())) { local.add(resolvedCall); @@ -56,7 +59,9 @@ import static org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor return null; } - public List> computePrioritizedTasks(@NotNull JetScope scope, @NotNull Call call, @NotNull String name, @NotNull BindingContext bindingContext, @NotNull DataFlowInfo dataFlowInfo) { + @NotNull + public List> computePrioritizedTasks(@NotNull JetScope scope, @NotNull Call call, @NotNull String name, + @NotNull BindingContext bindingContext, @NotNull DataFlowInfo dataFlowInfo) { List> result = Lists.newArrayList(); ReceiverDescriptor explicitReceiver = call.getExplicitReceiver(); @@ -68,7 +73,7 @@ import static org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor return result; } - + private void doComputeTasks(JetScope scope, ReceiverDescriptor receiver, Call call, String name, List> result, @NotNull AutoCastService autoCastService) { DataFlowInfo dataFlowInfo = autoCastService.getDataFlowInfo(); List implicitReceivers = Lists.newArrayList(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ValueArgumentsToParametersMapper.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ValueArgumentsToParametersMapper.java index 040a218e534..b3b722941fc 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ValueArgumentsToParametersMapper.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/ValueArgumentsToParametersMapper.java @@ -50,20 +50,17 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; JetReferenceExpression nameReference = valueArgument.getArgumentName().getReferenceExpression(); ValueParameterDescriptor valueParameterDescriptor = parameterByName.get(valueArgument.getArgumentName().getReferenceExpression().getReferencedName()); if (valueParameterDescriptor == null) { -// temporaryTrace.getErrorHandler().genericError(nameNode, "Cannot find a parameter with this name"); temporaryTrace.report(NAMED_PARAMETER_NOT_FOUND.on(nameReference)); error = true; } else { if (!usedParameters.add(valueParameterDescriptor)) { -// temporaryTrace.getErrorHandler().genericError(nameNode, "An argument is already passed for this parameter"); temporaryTrace.report(ARGUMENT_PASSED_TWICE.on(nameReference)); } temporaryTrace.record(REFERENCE_TARGET, nameReference, valueParameterDescriptor); put(candidateCall, valueParameterDescriptor, valueArgument, varargs); } if (somePositioned) { -// temporaryTrace.getErrorHandler().genericError(nameNode, "Mixing named and positioned arguments in not allowed"); temporaryTrace.report(MIXING_NAMED_AND_POSITIONED_ARGUMENTS.on(nameReference)); error = true; } @@ -71,7 +68,6 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; else { somePositioned = true; if (someNamed) { -// temporaryTrace.getErrorHandler().genericError(valueArgument.asElement().getNode(), "Mixing named and positioned arguments in not allowed"); temporaryTrace.report(MIXING_NAMED_AND_POSITIONED_ARGUMENTS.on(valueArgument.asElement())); error = true; } @@ -89,13 +85,11 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; usedParameters.add(valueParameterDescriptor); } else { -// temporaryTrace.getErrorHandler().genericError(valueArgument.asElement().getNode(), getTooManyArgumentsMessage(candidate)); temporaryTrace.report(TOO_MANY_ARGUMENTS.on(valueArgument.asElement(), candidate)); error = true; } } else { -// temporaryTrace.getErrorHandler().genericError(valueArgument.asElement().getNode(), getTooManyArgumentsMessage(candidate)); temporaryTrace.report(TOO_MANY_ARGUMENTS.on(valueArgument.asElement(), candidate)); error = true; } @@ -108,7 +102,6 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; JetExpression possiblyLabeledFunctionLiteral = functionLiteralArguments.get(0); if (valueParameters.isEmpty()) { -// temporaryTrace.getErrorHandler().genericError(possiblyLabeledFunctionLiteral.getNode(), getTooManyArgumentsMessage(candidate)); temporaryTrace.report(TOO_MANY_ARGUMENTS.on(possiblyLabeledFunctionLiteral, candidate)); error = true; } @@ -124,13 +117,11 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; ValueParameterDescriptor valueParameterDescriptor = valueParameters.get(valueParameters.size() - 1); if (valueParameterDescriptor.getVarargElementType() != null) { -// temporaryTrace.getErrorHandler().genericError(possiblyLabeledFunctionLiteral.getNode(), "Passing value as a vararg is only allowed inside a parenthesized argument list"); temporaryTrace.report(VARARG_OUTSIDE_PARENTHESES.on(possiblyLabeledFunctionLiteral)); error = true; } else { if (!usedParameters.add(valueParameterDescriptor)) { -// temporaryTrace.getErrorHandler().genericError(possiblyLabeledFunctionLiteral.getNode(), getTooManyArgumentsMessage(candidate)); temporaryTrace.report(TOO_MANY_ARGUMENTS.on(possiblyLabeledFunctionLiteral, candidate)); error = true; } @@ -142,7 +133,6 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; for (int i = 1; i < functionLiteralArguments.size(); i++) { JetExpression argument = functionLiteralArguments.get(i); -// temporaryTrace.getErrorHandler().genericError(argument.getNode(), "Only one function literal is allowed outside a parenthesized argument list"); temporaryTrace.report(MANY_FUNCTION_LITERAL_ARGUMENTS.on(argument)); error = true; } @@ -176,7 +166,8 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; error = true; } - assert candidateCall.getThisObject().exists() == candidateCall.getResultingDescriptor().getExpectedThisObject().exists() : "Shouldn't happen because of TaskPrioritizer: " + candidateCall.getCandidateDescriptor(); + assert candidateCall.getThisObject().exists() == candidateCall.getResultingDescriptor().getExpectedThisObject().exists() : + "Shouldn't happen because of TaskPrioritizer: " + candidateCall.getCandidateDescriptor(); return error; } @@ -196,8 +187,4 @@ import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; candidateCall.recordValueArgument(valueParameterDescriptor, argument); } } - -// private static String getTooManyArgumentsMessage(Descriptor candidate) { -// return "Too many arguments for " + DescriptorRenderer.TEXT.render(candidate); -// } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/inference/ConstraintResolutionListener.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/inference/ConstraintResolutionListener.java index 783f6591e32..6cbf129fb73 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/inference/ConstraintResolutionListener.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/inference/ConstraintResolutionListener.java @@ -10,6 +10,28 @@ import java.util.Set; */ public interface ConstraintResolutionListener { + public static final ConstraintResolutionListener DO_NOTHING = new ConstraintResolutionListener() { + @Override + public void constraintsForUnknown(TypeParameterDescriptor typeParameterDescriptor, ConstraintSystemImpl.TypeValue typeValue) { + } + + @Override + public void constraintsForKnownType(JetType type, ConstraintSystemImpl.TypeValue typeValue) { + } + + @Override + public void done(ConstraintSystemSolution solution, Set typeParameterDescriptors) { + } + + @Override + public void log(Object message) { + } + + @Override + public void error(Object message) { + } + }; + void constraintsForUnknown(TypeParameterDescriptor typeParameterDescriptor, ConstraintSystemImpl.TypeValue typeValue); void constraintsForKnownType(JetType type, ConstraintSystemImpl.TypeValue typeValue); void done(ConstraintSystemSolution solution, Set typeParameterDescriptors); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java index aa3123b79c8..73746161e66 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScope.java @@ -51,6 +51,11 @@ public interface JetScope { @Nullable PropertyDescriptor getPropertyByFieldReference(@NotNull String fieldName); + /** + * All visible descriptors from current scope. + * + * @return All visible descriptors from current scope. + */ @NotNull Collection getAllDescriptors(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeUtils.java new file mode 100644 index 00000000000..ce42890942b --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/scopes/JetScopeUtils.java @@ -0,0 +1,54 @@ +package org.jetbrains.jet.lang.resolve.scopes; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.CallableDescriptor; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * @author Nikolay Krasko + */ +public final class JetScopeUtils { + private JetScopeUtils() {} + + /** + * Get receivers in order of locality, so that the closest (the most local) receiver goes first + * A wrapper for {@link JetScope#getImplicitReceiversHierarchy(java.util.List)} + * + * @param scope Scope for getting receivers hierarchy. + * @return receivers hierarchy. + */ + @NotNull + public static Collection getImplicitReceiversHierarchy(@NotNull JetScope scope) { + List descriptors = Lists.newArrayList(); + scope.getImplicitReceiversHierarchy(descriptors); + return descriptors; + } + + /** + * Get all extension descriptors among visible descriptors for current scope. + * + * @param scope Scope for query extensions. + * @return extension descriptors. + */ + public static Collection getAllExtensions(@NotNull JetScope scope) { + final Set result = Sets.newHashSet(); + + for (DeclarationDescriptor descriptor : scope.getAllDescriptors()) { + if (descriptor instanceof CallableDescriptor) { + CallableDescriptor callDescriptor = (CallableDescriptor) descriptor; + if (callDescriptor.getReceiverParameter().exists()) { + result.add(callDescriptor); + } + } + } + + return result; + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingContext.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingContext.java index 9dcd5d3d314..c27e9f998cd 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingContext.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingContext.java @@ -12,9 +12,9 @@ import org.jetbrains.jet.lang.resolve.DescriptorResolver; import org.jetbrains.jet.lang.resolve.TypeResolver; import org.jetbrains.jet.lang.resolve.calls.CallMaker; import org.jetbrains.jet.lang.resolve.calls.CallResolver; +import org.jetbrains.jet.lang.resolve.calls.OverloadResolutionResults; import org.jetbrains.jet.lang.resolve.calls.ResolvedCall; import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo; -import org.jetbrains.jet.lang.resolve.calls.OverloadResolutionResults; import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstantResolver; import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; @@ -40,7 +40,8 @@ import java.util.Map; @NotNull JetType expectedType, @NotNull JetType expectedReturnType, boolean namespacesAllowed) { - return new ExpressionTypingContext(semanticServices, patternsToDataFlowInfo, patternsToBoundVariableLists, labelResolver, trace, scope, dataFlowInfo, expectedType, expectedReturnType, namespacesAllowed); + return new ExpressionTypingContext(semanticServices, patternsToDataFlowInfo, patternsToBoundVariableLists, + labelResolver, trace, scope, dataFlowInfo, expectedType, expectedReturnType, namespacesAllowed); } // @NotNull diff --git a/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java b/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java index 9fb52fa377c..3239d3c47ef 100644 --- a/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java +++ b/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java @@ -1,6 +1,9 @@ package org.jetbrains.jet.plugin.references; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.openapi.util.Iconable; @@ -14,12 +17,21 @@ import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.DescriptorUtils; +import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintResolutionListener; +import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystem; +import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystemImpl; +import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystemSolution; import org.jetbrains.jet.lang.resolve.scopes.JetScope; +import org.jetbrains.jet.lang.resolve.scopes.JetScopeUtils; +import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver; +import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.Variance; import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade; import org.jetbrains.jet.resolve.DescriptorRenderer; import java.util.List; +import java.util.Set; /** * @author yole @@ -53,9 +65,14 @@ class JetSimpleNameReference extends JetPsiReference { JetExpression receiverExpression = qualifiedExpression.getReceiverExpression(); JetFile file = (JetFile) myExpression.getContainingFile(); BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(file); + final JetType expressionType = bindingContext.get(BindingContext.EXPRESSION_TYPE, receiverExpression); - if (expressionType != null) { - return collectLookupElements(bindingContext, expressionType.getMemberScope()); + final JetScope resolutionScope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, receiverExpression); + + if (expressionType != null && resolutionScope != null) { + return collectLookupElements(bindingContext, + includeExternalCallableExtensions(expressionType.getMemberScope().getAllDescriptors(), + resolutionScope, new ExpressionReceiver(receiverExpression, expressionType))); } } else { @@ -63,7 +80,8 @@ class JetSimpleNameReference extends JetPsiReference { BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(file); JetScope resolutionScope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, myExpression); if (resolutionScope != null) { - return collectLookupElements(bindingContext, resolutionScope); + return collectLookupElements(bindingContext, + excludeNotCallableExtensions(resolutionScope.getAllDescriptors(), resolutionScope)); } } @@ -76,9 +94,43 @@ class JetSimpleNameReference extends JetPsiReference { return myExpression.getReferencedNameElement().replace(element); } - private Object[] collectLookupElements(BindingContext bindingContext, JetScope scope) { + private Iterable excludeNotCallableExtensions( + @NotNull Iterable descriptors, @NotNull final JetScope scope + ) { + final Set descriptorsSet = Sets.newHashSet(descriptors); + descriptorsSet.removeAll( + Collections2.filter(JetScopeUtils.getAllExtensions(scope), new Predicate() { + @Override + public boolean apply(CallableDescriptor callableDescriptor) { + return !checkReceiverResolution(scope.getImplicitReceiver(), callableDescriptor); + } + })); + + return descriptorsSet; + } + + private Iterable includeExternalCallableExtensions( + @NotNull Iterable descriptors, + @NotNull final JetScope externalScope, + @NotNull final ReceiverDescriptor receiverDescriptor + ) { + Set descriptorsSet = Sets.newHashSet(descriptors); + + descriptorsSet.addAll(Collections2.filter(JetScopeUtils.getAllExtensions(externalScope), + new Predicate() { + @Override + public boolean apply(CallableDescriptor callableDescriptor) { + return checkReceiverResolution(receiverDescriptor, callableDescriptor); + } + })); + + return descriptorsSet; + } + + private Object[] collectLookupElements(BindingContext bindingContext, Iterable descriptors) { List result = Lists.newArrayList(); - for (final DeclarationDescriptor descriptor : scope.getAllDescriptors()) { + + for (final DeclarationDescriptor descriptor : descriptors) { LookupElementBuilder element = LookupElementBuilder.create(descriptor.getName()); String typeText = ""; String tailText = ""; @@ -117,4 +169,29 @@ class JetSimpleNameReference extends JetPsiReference { } return result.toArray(); } + + /** + * Checks that receiver declaration could be resolved to call expected receiver. + */ + private static boolean checkReceiverResolution ( + @NotNull ReceiverDescriptor expectedReceiver, + @NotNull CallableDescriptor receiverArgument + ) { + ConstraintSystem constraintSystem = new ConstraintSystemImpl(ConstraintResolutionListener.DO_NOTHING); + for (TypeParameterDescriptor typeParameterDescriptor : receiverArgument.getTypeParameters()) { + constraintSystem.registerTypeVariable(typeParameterDescriptor, Variance.INVARIANT); + } + + ReceiverDescriptor receiverParameter = receiverArgument.getReceiverParameter(); + if (expectedReceiver.exists() && receiverParameter.exists()) { + constraintSystem.addSubtypingConstraint(expectedReceiver.getType(), receiverParameter.getType()); + } + else if (expectedReceiver.exists() || receiverParameter.exists()) { + // Only one of receivers exist + return false; + } + + ConstraintSystemSolution solution = constraintSystem.solve(); + return solution.getStatus().isSuccessful(); + } } diff --git a/idea/testData/completion/basic/extensions/ExtensionInExtendedClass.kt b/idea/testData/completion/basic/extensions/ExtensionInExtendedClass.kt new file mode 100644 index 00000000000..d4f4b62aa8a --- /dev/null +++ b/idea/testData/completion/basic/extensions/ExtensionInExtendedClass.kt @@ -0,0 +1,12 @@ +namespace Test + +class Some() { + fun methodName() { + + } +} + +fun Some.first() { +} + +// EXIST: first diff --git a/idea/testData/completion/basic/extensions/ExtensionInExtendedClassThis.kt b/idea/testData/completion/basic/extensions/ExtensionInExtendedClassThis.kt new file mode 100644 index 00000000000..a8f97b0ee84 --- /dev/null +++ b/idea/testData/completion/basic/extensions/ExtensionInExtendedClassThis.kt @@ -0,0 +1,10 @@ +class Some() { + fun methodName() { + this. + } +} + +fun Some.first() { +} + +// EXIST: first \ No newline at end of file diff --git a/idea/testData/completion/basic/extensions/ExtensionInExtension.kt b/idea/testData/completion/basic/extensions/ExtensionInExtension.kt new file mode 100644 index 00000000000..9365ddaa7e4 --- /dev/null +++ b/idea/testData/completion/basic/extensions/ExtensionInExtension.kt @@ -0,0 +1,11 @@ +fun Some.first() { +} + +class Some() { +} + +fun Some.second() { + +} + +// EXIST: first, second \ No newline at end of file diff --git a/idea/testData/completion/basic/extensions/ExtensionInExtensionThis.kt b/idea/testData/completion/basic/extensions/ExtensionInExtensionThis.kt new file mode 100644 index 00000000000..f5eabe4ee0b --- /dev/null +++ b/idea/testData/completion/basic/extensions/ExtensionInExtensionThis.kt @@ -0,0 +1,11 @@ +fun Some.first() { +} + +class Some() { +} + +fun Some.second() { + this. +} + +// EXIST: first, second \ No newline at end of file diff --git a/idea/testData/completion/basic/extensions/InvalidTypeParameters.kt b/idea/testData/completion/basic/extensions/InvalidTypeParameters.kt new file mode 100644 index 00000000000..7dd4fee1e9a --- /dev/null +++ b/idea/testData/completion/basic/extensions/InvalidTypeParameters.kt @@ -0,0 +1,17 @@ +class SomeObject() { + var field : T? = null +} + +class A {} +class C {} + +fun , U> SomeObject.compareTo(other : SomeObject) : Int { + return 0; +} + +fun some() { + val test = SomeObject + test. +} + +// ABSENT: compareTo \ No newline at end of file diff --git a/idea/testData/completion/basic/extensions/IrrelevantExtension.kt b/idea/testData/completion/basic/extensions/IrrelevantExtension.kt new file mode 100644 index 00000000000..808583c66eb --- /dev/null +++ b/idea/testData/completion/basic/extensions/IrrelevantExtension.kt @@ -0,0 +1,11 @@ +fun Some.simpleKotlinExtension() { +} + +class Some() { +} + +fun test() { + +} + +// ABSENT: simpleKotlinExtension \ No newline at end of file diff --git a/idea/testData/completion/basic/extensions/JavaTypeExtension.kt b/idea/testData/completion/basic/extensions/JavaTypeExtension.kt new file mode 100644 index 00000000000..9f8e7ff4945 --- /dev/null +++ b/idea/testData/completion/basic/extensions/JavaTypeExtension.kt @@ -0,0 +1,10 @@ +fun java.lang.Iterable.first() : T? { + return this.iterator()?.next() +} + +fun main(args : Array) { + val test = java.util.HashSet() + test. +} + +// EXIST: first diff --git a/idea/testData/completion/basic/extensions/KotlinGenericTypeExtension.kt b/idea/testData/completion/basic/extensions/KotlinGenericTypeExtension.kt new file mode 100644 index 00000000000..8352c7c5cd2 --- /dev/null +++ b/idea/testData/completion/basic/extensions/KotlinGenericTypeExtension.kt @@ -0,0 +1,12 @@ +fun Some.close() { +} + +class Some() { +} + +fun test() { + val s = Some() + s. +} + +// EXIST: close \ No newline at end of file diff --git a/idea/testData/completion/basic/extensions/KotlinTypeExtension.kt b/idea/testData/completion/basic/extensions/KotlinTypeExtension.kt new file mode 100644 index 00000000000..0707b815317 --- /dev/null +++ b/idea/testData/completion/basic/extensions/KotlinTypeExtension.kt @@ -0,0 +1,12 @@ +fun Some.simpleKotlinExtension() { +} + +class Some() { +} + +fun test() { + val s = Some() + s. +} + +// EXIST: simpleKotlinExtension \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/completion/ExtensionsCompletionTest.java b/idea/tests/org/jetbrains/jet/completion/ExtensionsCompletionTest.java new file mode 100644 index 00000000000..5484200d560 --- /dev/null +++ b/idea/tests/org/jetbrains/jet/completion/ExtensionsCompletionTest.java @@ -0,0 +1,43 @@ +package org.jetbrains.jet.completion; + +import junit.framework.Test; +import junit.framework.TestSuite; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.JetTestCaseBuilder; +import org.jetbrains.jet.plugin.PluginTestCaseBase; + +import java.io.File; + +/** + * @author Nikolay Krasko + */ +public class ExtensionsCompletionTest extends JetCompletionTestBase { + +// public ExtensionsCompletionTest() { +// this("/completion/basic/extensions", "IrrelevantExtension"); +// // this("/completion/basic/extensions", "InvalidTypeParameters"); +// // this("/completion/basic/extensions", "ExtensionInExtensionThis"); +// } + + protected ExtensionsCompletionTest(@NotNull String path, @NotNull String name) { + super(path, name); + } + + @NotNull + public static TestSuite suite() { + TestSuite suite = new TestSuite(); + + JetTestCaseBuilder.appendTestsInDirectory( + PluginTestCaseBase.getTestDataPathBase(), "/completion/basic/extensions", false, + JetTestCaseBuilder.emptyFilter, new JetTestCaseBuilder.NamedTestFactory() { + + @NotNull + @Override + public Test createTest(@NotNull String dataPath, @NotNull String name, @NotNull File file) { + return new ExtensionsCompletionTest(dataPath, name); + } + }, suite); + + return suite; + } +} diff --git a/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java b/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java index 995363c1be8..4bb6dd31b89 100644 --- a/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java +++ b/idea/tests/org/jetbrains/jet/completion/JetBasicCompletionTest.java @@ -12,31 +12,9 @@ import java.io.File; * @author Nikolay.Krasko */ public class JetBasicCompletionTest extends JetCompletionTestBase { - private final String myPath; - private final String myName; - public JetBasicCompletionTest(@NotNull String path, @NotNull String name) { - myPath = path; - myName = name; - - // Set name explicitly because otherwise there will be "TestCase.fName cannot be null" - setName("testCompletionExecute"); - } - - public void testCompletionExecute() throws Exception { - doTest(); - } - - @Override - protected String getTestDataPath() { - return new File(PluginTestCaseBase.getTestDataPathBase(), myPath).getPath() + - File.separator; - } - - @NotNull - @Override - public String getName() { - return "test" + myName; + protected JetBasicCompletionTest(@NotNull String path, @NotNull String name) { + super(path, name); } @NotNull diff --git a/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java b/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java index 39372cd71c2..624eb685c3e 100644 --- a/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java +++ b/idea/tests/org/jetbrains/jet/completion/JetCompletionTestBase.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.plugin.PluginTestCaseBase; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; @@ -20,6 +21,32 @@ import java.util.List; * @author Nikolay.Krasko */ public abstract class JetCompletionTestBase extends LightCompletionTestCase { + private final String myPath; + private final String myName; + + protected JetCompletionTestBase(@NotNull String path, @NotNull String name) { + myPath = path; + myName = name; + + // Set name explicitly because otherwise there will be "TestCase.fName cannot be null" + setName("testCompletionExecute"); + } + + public void testCompletionExecute() throws Exception { + doTest(); + } + + @Override + protected String getTestDataPath() { + return new File(PluginTestCaseBase.getTestDataPathBase(), myPath).getPath() + + File.separator; + } + + @NotNull + @Override + public String getName() { + return "test" + myName; + } private CompletionType type; diff --git a/idea/tests/org/jetbrains/jet/completion/KeywordsCompletionTest.java b/idea/tests/org/jetbrains/jet/completion/KeywordsCompletionTest.java index 4a0e14d9ed4..cc45d46b28b 100644 --- a/idea/tests/org/jetbrains/jet/completion/KeywordsCompletionTest.java +++ b/idea/tests/org/jetbrains/jet/completion/KeywordsCompletionTest.java @@ -15,31 +15,8 @@ import java.io.File; */ public class KeywordsCompletionTest extends JetCompletionTestBase { - private final String myPath; - private final String myName; - - public KeywordsCompletionTest(@NotNull String path, @NotNull String name) { - myPath = path; - myName = name; - - // Set name explicitly because otherwise there will be "TestCase.fName cannot be null" - setName("testCompletionExecute"); - } - - public void testCompletionExecute() throws Exception { - doTest(); - } - - @Override - protected String getTestDataPath() { - return new File(PluginTestCaseBase.getTestDataPathBase(), myPath).getPath() + - File.separator; - } - - @NotNull - @Override - public String getName() { - return "test" + myName; + protected KeywordsCompletionTest(@NotNull String path, @NotNull String name) { + super(path, name); } @NotNull