KT-768 Code completion for extension functions

This commit is contained in:
Nikolay Krasko
2011-12-12 12:12:45 +04:00
parent 1260ba7a5f
commit 182009ab61
26 changed files with 374 additions and 82 deletions
@@ -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