Code completion: graying members after dot completion for nullable value
This commit is contained in:
+52
-17
@@ -36,12 +36,28 @@ import org.jetbrains.jet.plugin.util.extensionsUtils.isExtensionCallable
|
||||
import org.jetbrains.jet.lang.types.JetType
|
||||
import org.jetbrains.jet.lang.types.TypeUtils
|
||||
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
|
||||
import org.jetbrains.jet.lexer.JetTokens
|
||||
|
||||
public class ReferenceVariantsHelper(
|
||||
private val context: BindingContext,
|
||||
private val visibilityFilter: (DeclarationDescriptor) -> Boolean
|
||||
) {
|
||||
|
||||
public enum class CallType {
|
||||
NORMAL
|
||||
INFIX
|
||||
SAFE
|
||||
}
|
||||
|
||||
public data class ReceiversData(
|
||||
public val receivers: Collection<ReceiverValue>,
|
||||
public val callType: CallType
|
||||
) {
|
||||
class object {
|
||||
val Empty = ReceiversData(listOf(), CallType.NORMAL)
|
||||
}
|
||||
}
|
||||
|
||||
public fun getReferenceVariants(
|
||||
expression: JetSimpleNameExpression,
|
||||
kindFilter: DescriptorKindFilter,
|
||||
@@ -65,11 +81,11 @@ public class ReferenceVariantsHelper(
|
||||
return resolutionScope.getDescriptorsFiltered(restrictedFilter, nameFilter)
|
||||
}
|
||||
|
||||
val receiverExpression = getReferenceVariantsReceiver(expression)
|
||||
if (receiverExpression != null) {
|
||||
val isInfixCall = parent is JetBinaryExpression
|
||||
val pair = getReferenceVariantsReceiver(expression)
|
||||
if (pair != null) {
|
||||
val (receiverExpression, callType) = pair
|
||||
fun filterIfInfix(descriptor: DeclarationDescriptor)
|
||||
= if (isInfixCall) descriptor is SimpleFunctionDescriptor && descriptor.getValueParameters().size == 1 else true
|
||||
= if (callType == CallType.INFIX) descriptor is SimpleFunctionDescriptor && descriptor.getValueParameters().size == 1 else true
|
||||
|
||||
// Process as call expression
|
||||
val descriptors = HashSet<DeclarationDescriptor>()
|
||||
@@ -93,7 +109,7 @@ public class ReferenceVariantsHelper(
|
||||
variant.getMemberScope().getDescriptorsFiltered(mask, nameFilter).filterTo(descriptors, ::filterIfInfix)
|
||||
}
|
||||
|
||||
descriptors.addCallableExtensions(resolutionScope, receiverValue, dataFlowInfo, isInfixCall, kindFilter, nameFilter)
|
||||
descriptors.addCallableExtensions(resolutionScope, receiverValue, dataFlowInfo, callType == CallType.INFIX, kindFilter, nameFilter)
|
||||
}
|
||||
|
||||
return descriptors
|
||||
@@ -120,15 +136,16 @@ public class ReferenceVariantsHelper(
|
||||
}
|
||||
}
|
||||
|
||||
public fun getReferenceVariantsReceivers(expression: JetSimpleNameExpression): Collection<ReceiverValue> {
|
||||
val receiverExpression = getReferenceVariantsReceiver(expression)
|
||||
if (receiverExpression != null) {
|
||||
val expressionType = context[BindingContext.EXPRESSION_TYPE, receiverExpression] ?: return listOf()
|
||||
return listOf(ExpressionReceiver(receiverExpression, expressionType))
|
||||
public fun getReferenceVariantsReceivers(expression: JetSimpleNameExpression): ReceiversData {
|
||||
val receiverData = getReferenceVariantsReceiver(expression)
|
||||
if (receiverData != null) {
|
||||
val receiverExpression = receiverData.first
|
||||
val expressionType = context[BindingContext.EXPRESSION_TYPE, receiverExpression] ?: return ReceiversData.Empty
|
||||
return ReceiversData(listOf(ExpressionReceiver(receiverExpression, expressionType)), receiverData.second)
|
||||
}
|
||||
else {
|
||||
val resolutionScope = context[BindingContext.RESOLUTION_SCOPE, expression] ?: return listOf()
|
||||
return resolutionScope.getImplicitReceiversHierarchy().map { it.getValue() }
|
||||
val resolutionScope = context[BindingContext.RESOLUTION_SCOPE, expression] ?: return ReceiversData.Empty
|
||||
return ReceiversData(resolutionScope.getImplicitReceiversHierarchy().map { it.getValue() }, CallType.NORMAL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,15 +158,33 @@ public class ReferenceVariantsHelper(
|
||||
return type
|
||||
}
|
||||
|
||||
private fun getReferenceVariantsReceiver(expression: JetSimpleNameExpression): JetExpression? {
|
||||
private fun getReferenceVariantsReceiver(expression: JetSimpleNameExpression): Pair<JetExpression, CallType>? {
|
||||
val parent = expression.getParent()
|
||||
val inPositionForCompletionWithReceiver = parent is JetCallExpression
|
||||
|| parent is JetQualifiedExpression
|
||||
|| parent is JetBinaryExpression
|
||||
return if (inPositionForCompletionWithReceiver)
|
||||
expression.getReceiverExpression()
|
||||
else
|
||||
null
|
||||
if (!inPositionForCompletionWithReceiver) return null
|
||||
val receiverExpression = expression.getReceiverExpression() ?: return null
|
||||
val callType = when (parent) {
|
||||
is JetBinaryExpression -> CallType.INFIX
|
||||
|
||||
is JetCallExpression -> {
|
||||
if ((parent.getParent() as JetQualifiedExpression).getOperationSign() == JetTokens.SAFE_ACCESS)
|
||||
CallType.SAFE
|
||||
else
|
||||
CallType.NORMAL
|
||||
}
|
||||
|
||||
is JetQualifiedExpression -> {
|
||||
if (parent.getOperationSign() == JetTokens.SAFE_ACCESS)
|
||||
CallType.SAFE
|
||||
else
|
||||
CallType.NORMAL
|
||||
}
|
||||
|
||||
else -> error("Unknown parent")
|
||||
}
|
||||
return receiverExpression to callType
|
||||
}
|
||||
|
||||
private fun MutableCollection<DeclarationDescriptor>.addCallableExtensions(
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jetbrains.jet.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.jet.plugin.refactoring.comparePossiblyOverridingDescriptors
|
||||
import kotlin.properties.Delegates
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.jet.plugin.util.makeNotNullable
|
||||
|
||||
class CompletionSessionConfiguration(
|
||||
val completeNonImportedDeclarations: Boolean,
|
||||
@@ -74,11 +75,14 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
|
||||
protected val boldImmediateLookupElementFactory: LookupElementFactory = run {
|
||||
if (jetReference != null) {
|
||||
val expression = jetReference.expression
|
||||
val receivers = referenceVariantsHelper!!.getReferenceVariantsReceivers(expression)
|
||||
val (receivers, callType) = referenceVariantsHelper!!.getReferenceVariantsReceivers(expression)
|
||||
val dataFlowInfo = bindingContext!!.getDataFlowInfo(expression)
|
||||
val receiverTypes = receivers.flatMap {
|
||||
var receiverTypes = receivers.flatMap {
|
||||
SmartCastUtils.getSmartCastVariantsWithLessSpecificExcluded(it, bindingContext, dataFlowInfo)
|
||||
}
|
||||
if (callType == ReferenceVariantsHelper.CallType.SAFE) {
|
||||
receiverTypes = receiverTypes.map { it.makeNotNullable() }
|
||||
}
|
||||
BoldImmediateLookupElementFactory(receiverTypes)
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.intellij.codeInsight.lookup.LookupElementDecorator
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation
|
||||
import org.jetbrains.jet.lang.types.JetType
|
||||
import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade
|
||||
import java.awt.Color
|
||||
|
||||
public open class LookupElementFactory protected() {
|
||||
public open fun createLookupElement(resolutionFacade: ResolutionFacade, descriptor: DeclarationDescriptor): LookupElement {
|
||||
@@ -162,19 +163,44 @@ public class BoldImmediateLookupElementFactory(private val receiverTypes: Collec
|
||||
val element = super.createLookupElement(resolutionFacade, descriptor)
|
||||
|
||||
if (descriptor !is CallableMemberDescriptor) return element
|
||||
|
||||
val isReceiverNullable = receiverTypes.all { it.isNullable() }
|
||||
val receiverParameter = descriptor.getExtensionReceiverParameter()
|
||||
val bold = if (receiverParameter != null) {
|
||||
receiverTypes.any { it == receiverParameter.getType() }
|
||||
|
||||
val style: Style = if (receiverParameter != null) {
|
||||
val receiverParamType = receiverParameter.getType()
|
||||
if (isReceiverNullable && !receiverParamType.isNullable())
|
||||
Style.GRAYED
|
||||
else if (receiverTypes.any { it == receiverParamType })
|
||||
Style.BOLD
|
||||
else
|
||||
Style.NORMAL
|
||||
}
|
||||
else {
|
||||
descriptor.getContainingDeclaration() is ClassifierDescriptor && descriptor.getKind() == CallableMemberDescriptor.Kind.DECLARATION
|
||||
if (isReceiverNullable)
|
||||
Style.GRAYED
|
||||
else if (descriptor.getContainingDeclaration() is ClassifierDescriptor && descriptor.getKind() == CallableMemberDescriptor.Kind.DECLARATION)
|
||||
Style.BOLD
|
||||
else
|
||||
Style.NORMAL
|
||||
}
|
||||
|
||||
return if (bold) {
|
||||
return if (style != Style.NORMAL) {
|
||||
object : LookupElementDecorator<LookupElement>(element) {
|
||||
override fun renderElement(presentation: LookupElementPresentation) {
|
||||
super.renderElement(presentation)
|
||||
presentation.setItemTextBold(true)
|
||||
if (style == Style.BOLD) {
|
||||
presentation.setItemTextBold(true)
|
||||
}
|
||||
else {
|
||||
presentation.setItemTextForeground(Color.GRAY)
|
||||
// gray all tail fragments too:
|
||||
val fragments = presentation.getTailFragments()
|
||||
presentation.clearTail()
|
||||
for (fragment in fragments) {
|
||||
presentation.appendTailText(fragment.text, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,4 +208,10 @@ public class BoldImmediateLookupElementFactory(private val receiverTypes: Collec
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
private enum class Style {
|
||||
NORMAL
|
||||
BOLD
|
||||
GRAYED
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
fun String?.forNullableString(){}
|
||||
fun Any?.forNullableAny(){}
|
||||
fun String.forString(){}
|
||||
fun Any.forAny(){}
|
||||
|
||||
fun foo(s: String?) {
|
||||
s.<caret>
|
||||
}
|
||||
|
||||
// EXIST: { lookupString: "forNullableString", attributes: "bold" }
|
||||
// EXIST: { lookupString: "forNullableAny", attributes: "" }
|
||||
// EXIST: { lookupString: "forString", attributes: "grayed" }
|
||||
// EXIST: { lookupString: "forAny", attributes: "grayed" }
|
||||
// EXIST: { lookupString: "compareTo", attributes: "grayed" }
|
||||
@@ -0,0 +1,16 @@
|
||||
fun String?.forNullableString(){}
|
||||
fun Any?.forNullableAny(){}
|
||||
fun String.forString(){}
|
||||
fun Any.forAny(){}
|
||||
|
||||
fun foo(o: Any?) {
|
||||
if (o is String) {
|
||||
o.<caret>
|
||||
}
|
||||
}
|
||||
|
||||
// EXIST: { lookupString: "forNullableString", attributes: "" }
|
||||
// EXIST: { lookupString: "forNullableAny", attributes: "" }
|
||||
// EXIST: { lookupString: "forString", attributes: "bold" }
|
||||
// EXIST: { lookupString: "forAny", attributes: "" }
|
||||
// EXIST: { lookupString: "compareTo", attributes: "bold" }
|
||||
@@ -0,0 +1,30 @@
|
||||
trait T1 {
|
||||
fun inT1(){}
|
||||
}
|
||||
|
||||
trait T2 {
|
||||
fun inT2(){}
|
||||
}
|
||||
|
||||
fun T1.forT1(){}
|
||||
fun T2.forT2(){}
|
||||
fun T1?.forNullableT1(){}
|
||||
fun T2?.forNullableT2(){}
|
||||
fun Any.forAny(){}
|
||||
fun Any?.forNullableAny(){}
|
||||
|
||||
fun foo(o: T1?) {
|
||||
if (o is T2) {
|
||||
o.<caret>
|
||||
}
|
||||
}
|
||||
|
||||
// EXIST: { lookupString: "inT1", attributes: "bold" }
|
||||
// EXIST: { lookupString: "inT2", attributes: "bold" }
|
||||
// EXIST: { lookupString: "hashCode", attributes: "" }
|
||||
// EXIST: { lookupString: "forT1", attributes: "bold" }
|
||||
// EXIST: { lookupString: "forT2", attributes: "bold" }
|
||||
// EXIST: { lookupString: "forNullableT1", attributes: "" }
|
||||
// EXIST: { lookupString: "forNullableT2", attributes: "" }
|
||||
// EXIST: { lookupString: "forAny", attributes: "" }
|
||||
// EXIST: { lookupString: "forNullableAny", attributes: "" }
|
||||
@@ -0,0 +1,14 @@
|
||||
fun String?.forNullableString(){}
|
||||
fun Any?.forNullableAny(){}
|
||||
fun String.forString(){}
|
||||
fun Any.forAny(){}
|
||||
|
||||
fun foo(s: String?) {
|
||||
s?.<caret>
|
||||
}
|
||||
|
||||
// EXIST: { lookupString: "forNullableString", attributes: "" }
|
||||
// EXIST: { lookupString: "forNullableAny", attributes: "" }
|
||||
// EXIST: { lookupString: "forString", attributes: "bold" }
|
||||
// EXIST: { lookupString: "forAny", attributes: "" }
|
||||
// EXIST: { lookupString: "compareTo", attributes: "bold" }
|
||||
@@ -26,6 +26,7 @@ import com.google.gson.JsonParser;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -34,7 +35,9 @@ import org.jetbrains.jet.plugin.project.TargetPlatform;
|
||||
import org.jetbrains.jet.plugin.stubs.AstAccessControl;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Extract a number of statements about completion from the given text. Those statements
|
||||
@@ -321,6 +324,12 @@ public class ExpectedCompletionUtils {
|
||||
if (builder.length() > 0) builder.append(" ");
|
||||
builder.append("underlined");
|
||||
}
|
||||
Color foreground = presentation.getItemTextForeground();
|
||||
if (!foreground.equals(JBColor.foreground())) {
|
||||
assert foreground.equals(Color.GRAY);
|
||||
if (builder.length() > 0) builder.append(" ");
|
||||
builder.append("grayed");
|
||||
}
|
||||
if (presentation.isStrikeout()) {
|
||||
if (builder.length() > 0) builder.append(" ");
|
||||
builder.append("strikeout");
|
||||
|
||||
@@ -48,6 +48,24 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AfterNullable.kt")
|
||||
public void testAfterNullable() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/AfterNullable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AfterNullableAutoCast.kt")
|
||||
public void testAfterNullableAutoCast() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/AfterNullableAutoCast.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AfterNullableAutoCast2.kt")
|
||||
public void testAfterNullableAutoCast2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/AfterNullableAutoCast2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInCommon() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/basic/common"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
@@ -730,6 +748,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("SafeCallAfterNullable.kt")
|
||||
public void testSafeCallAfterNullable() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SafeCallAfterNullable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ShortClassNamesInTypePosition.kt")
|
||||
public void testShortClassNamesInTypePosition() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/ShortClassNamesInTypePosition.kt");
|
||||
|
||||
@@ -48,6 +48,24 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AfterNullable.kt")
|
||||
public void testAfterNullable() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/AfterNullable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AfterNullableAutoCast.kt")
|
||||
public void testAfterNullableAutoCast() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/AfterNullableAutoCast.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AfterNullableAutoCast2.kt")
|
||||
public void testAfterNullableAutoCast2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/AfterNullableAutoCast2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInCommon() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/basic/common"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
@@ -730,6 +748,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("SafeCallAfterNullable.kt")
|
||||
public void testSafeCallAfterNullable() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SafeCallAfterNullable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ShortClassNamesInTypePosition.kt")
|
||||
public void testShortClassNamesInTypePosition() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/ShortClassNamesInTypePosition.kt");
|
||||
|
||||
Reference in New Issue
Block a user