KT-768 Code completion for extension functions
This commit is contained in:
@@ -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<DeclarationDescriptor> excludeNotCallableExtensions(
|
||||
@NotNull Iterable<DeclarationDescriptor> descriptors, @NotNull final JetScope scope
|
||||
) {
|
||||
final Set<DeclarationDescriptor> descriptorsSet = Sets.newHashSet(descriptors);
|
||||
descriptorsSet.removeAll(
|
||||
Collections2.filter(JetScopeUtils.getAllExtensions(scope), new Predicate<CallableDescriptor>() {
|
||||
@Override
|
||||
public boolean apply(CallableDescriptor callableDescriptor) {
|
||||
return !checkReceiverResolution(scope.getImplicitReceiver(), callableDescriptor);
|
||||
}
|
||||
}));
|
||||
|
||||
return descriptorsSet;
|
||||
}
|
||||
|
||||
private Iterable<DeclarationDescriptor> includeExternalCallableExtensions(
|
||||
@NotNull Iterable<DeclarationDescriptor> descriptors,
|
||||
@NotNull final JetScope externalScope,
|
||||
@NotNull final ReceiverDescriptor receiverDescriptor
|
||||
) {
|
||||
Set<DeclarationDescriptor> descriptorsSet = Sets.newHashSet(descriptors);
|
||||
|
||||
descriptorsSet.addAll(Collections2.filter(JetScopeUtils.getAllExtensions(externalScope),
|
||||
new Predicate<CallableDescriptor>() {
|
||||
@Override
|
||||
public boolean apply(CallableDescriptor callableDescriptor) {
|
||||
return checkReceiverResolution(receiverDescriptor, callableDescriptor);
|
||||
}
|
||||
}));
|
||||
|
||||
return descriptorsSet;
|
||||
}
|
||||
|
||||
private Object[] collectLookupElements(BindingContext bindingContext, Iterable<DeclarationDescriptor> descriptors) {
|
||||
List<LookupElement> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Test
|
||||
|
||||
class Some() {
|
||||
fun methodName() {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
|
||||
fun Some.first() {
|
||||
}
|
||||
|
||||
// EXIST: first
|
||||
@@ -0,0 +1,10 @@
|
||||
class Some() {
|
||||
fun methodName() {
|
||||
this.<caret>
|
||||
}
|
||||
}
|
||||
|
||||
fun Some.first() {
|
||||
}
|
||||
|
||||
// EXIST: first
|
||||
@@ -0,0 +1,11 @@
|
||||
fun Some.first() {
|
||||
}
|
||||
|
||||
class Some() {
|
||||
}
|
||||
|
||||
fun Some.second() {
|
||||
<caret>
|
||||
}
|
||||
|
||||
// EXIST: first, second
|
||||
@@ -0,0 +1,11 @@
|
||||
fun Some.first() {
|
||||
}
|
||||
|
||||
class Some() {
|
||||
}
|
||||
|
||||
fun Some.second() {
|
||||
this.<caret>
|
||||
}
|
||||
|
||||
// EXIST: first, second
|
||||
@@ -0,0 +1,17 @@
|
||||
class SomeObject<T, U>() {
|
||||
var field : T? = null
|
||||
}
|
||||
|
||||
class A {}
|
||||
class C {}
|
||||
|
||||
fun <T: Comparable<T>, U> SomeObject<T, U>.compareTo(other : SomeObject<T, U>) : Int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fun some() {
|
||||
val test = SomeObject<A, A>
|
||||
test.<caret>
|
||||
}
|
||||
|
||||
// ABSENT: compareTo
|
||||
@@ -0,0 +1,11 @@
|
||||
fun Some.simpleKotlinExtension() {
|
||||
}
|
||||
|
||||
class Some() {
|
||||
}
|
||||
|
||||
fun test() {
|
||||
<caret>
|
||||
}
|
||||
|
||||
// ABSENT: simpleKotlinExtension
|
||||
@@ -0,0 +1,10 @@
|
||||
fun <T> java.lang.Iterable<T>.first() : T? {
|
||||
return this.iterator()?.next()
|
||||
}
|
||||
|
||||
fun main(args : Array<String>) {
|
||||
val test = java.util.HashSet<Int>()
|
||||
test.<caret>
|
||||
}
|
||||
|
||||
// EXIST: first
|
||||
@@ -0,0 +1,12 @@
|
||||
fun <T> Some<T>.close() {
|
||||
}
|
||||
|
||||
class Some<T>() {
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val s = Some<String>()
|
||||
s.<caret>
|
||||
}
|
||||
|
||||
// EXIST: close
|
||||
@@ -0,0 +1,12 @@
|
||||
fun Some.simpleKotlinExtension() {
|
||||
}
|
||||
|
||||
class Some() {
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val s = Some()
|
||||
s.<caret>
|
||||
}
|
||||
|
||||
// EXIST: simpleKotlinExtension
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user