KT-6191 Support completion for backing field

#KT-6191 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-03-05 17:58:49 +03:00
parent 86272621de
commit 620fb8683b
15 changed files with 329 additions and 60 deletions
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.completion.*
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.patterns.CharPattern
import com.intellij.patterns.ElementPattern
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns
import com.intellij.psi.PsiElement
@@ -37,10 +38,12 @@ import org.jetbrains.kotlin.idea.refactoring.comparePossiblyOverridingDescriptor
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptorKindExclude
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastUtils
@@ -90,6 +93,7 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
protected val bindingContext: BindingContext? = expression?.let { resolutionFacade.analyze(it, BodyResolveMode.PARTIAL_FOR_COMPLETION) }
protected val inDescriptor: DeclarationDescriptor? = expression?.let { bindingContext!!.get(BindingContext.RESOLUTION_SCOPE, it)?.getContainingDeclaration() }
private fun singleCharPattern(char: Char): CharPattern {
return StandardPatterns.character().with(
object : PatternCondition<Char>(char.toString()) {
@@ -97,8 +101,20 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
})
}
private val kotlinIdentifierStartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierStart(), StandardPatterns.not(singleCharPattern('$')))
private val kotlinIdentifierPartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierPart(), StandardPatterns.not(singleCharPattern('$')))
private val kotlinIdentifierStartPattern: ElementPattern<Char>
private val kotlinIdentifierPartPattern: ElementPattern<Char>
;{
val includeDollar = position.prevLeaf()?.getNode()?.getElementType() != JetTokens.SHORT_TEMPLATE_ENTRY_START
if (includeDollar) {
kotlinIdentifierStartPattern = StandardPatterns.character().javaIdentifierStart()
kotlinIdentifierPartPattern = StandardPatterns.character().javaIdentifierPart()
}
else {
kotlinIdentifierStartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierStart(), StandardPatterns.not(singleCharPattern('$')))
kotlinIdentifierPartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierPart(), StandardPatterns.not(singleCharPattern('$')))
}
}
protected val prefix: String = CompletionUtil.findIdentifierPrefix(
parameters.getPosition().getContainingFile(),
@@ -135,11 +151,10 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
}
protected val collector: LookupElementsCollector = LookupElementsCollector(
prefixMatcher, parameters, resolutionFacade, lookupElementFactory, expression?.getParent() is JetSimpleNameStringTemplateEntry)
prefixMatcher, parameters, resolutionFacade, lookupElementFactory, inDescriptor, expression?.getParent() is JetSimpleNameStringTemplateEntry)
protected val project: Project = position.getProject()
protected val originalSearchScope: GlobalSearchScope = parameters.getOriginalFile().getResolveScope()
// we need to exclude the original file from scope because our resolve session is built with this file replaced by synthetic one
@@ -378,7 +393,7 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para
if (expression != null) {
val mapper = ToFromOriginalFileMapper(parameters.getOriginalFile() as JetFile, position.getContainingFile() as JetFile, parameters.getOffset())
val completion = SmartCompletion(expression, resolutionFacade, moduleDescriptor,
bindingContext!!, { isVisibleDescriptor(it) }, prefixMatcher, originalSearchScope,
bindingContext!!, { isVisibleDescriptor(it) }, inDescriptor, prefixMatcher, originalSearchScope,
mapper, lookupElementFactory)
val result = completion.execute()
if (result != null) {
@@ -16,58 +16,38 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.openapi.util.Key
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.AutoCompletionPolicy
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.codeInsight.completion.CompletionProgressIndicator
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import com.intellij.codeInsight.completion.PrefixMatcher
import org.jetbrains.kotlin.name.Name
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.codeInsight.completion.InsertionContext
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.psi.JetFunctionLiteral
import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression
import org.jetbrains.kotlin.psi.JetValueArgument
import org.jetbrains.kotlin.psi.JetValueArgumentList
import org.jetbrains.kotlin.psi.JetCallExpression
import org.jetbrains.kotlin.psi.JetExpression
import java.util.ArrayList
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.psi.JetLabeledExpression
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.JetReferenceExpression
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.psi.JetDeclarationWithBody
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.*
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.Key
import com.intellij.util.PlatformIcons
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.psi.JetLoopExpression
import org.jetbrains.kotlin.renderer.DescriptorRendererBuilder
import org.jetbrains.kotlin.renderer.NameShortness
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import java.util.ArrayList
enum class ItemPriority {
MULTIPLE_ARGUMENTS_ITEM
DEFAULT
BACKING_FIELD
NAMED_PARAMETER
}
@@ -121,7 +101,20 @@ fun rethrowWithCancelIndicator(exception: ProcessCanceledException): ProcessCanc
return exception
}
fun PrefixMatcher.asNameFilter() = { (name: Name) -> !name.isSpecial() && prefixMatches(name.getIdentifier()) }
fun PrefixMatcher.asNameFilter() = { (name: Name) ->
if (name.isSpecial()) {
false
}
else {
val identifier = name.getIdentifier()
if (getPrefix().startsWith("$")) { // we need properties from scope for backing field completion
prefixMatches("$" + identifier)
}
else {
prefixMatches(identifier)
}
}
}
fun LookupElementPresentation.prependTailText(text: String, grayed: Boolean) {
val tails = getTailFragments()
@@ -354,3 +347,36 @@ fun breakOrContinueExpressionItems(position: JetElement, breakOrContinue: String
}
return result
}
fun LookupElementFactory.createBackingFieldLookupElement(
property: PropertyDescriptor,
inDescriptor: DeclarationDescriptor?,
resolutionFacade: ResolutionFacade
): LookupElement? {
if (inDescriptor == null) return null // no backing field accessible
val insideAccessor = inDescriptor is PropertyAccessorDescriptor && inDescriptor.getCorrespondingProperty() == property
if (!insideAccessor) {
val container = property.getContainingDeclaration()
if (container !is ClassDescriptor || !DescriptorUtils.isAncestor(container, inDescriptor, false)) return null // backing field not accessible
}
val declaration = (DescriptorToSourceUtils.descriptorToDeclaration(property) as? JetProperty) ?: return null
val accessors = declaration.getAccessors()
if (accessors.all { it.getBodyExpression() == null }) return null // makes no sense to access backing field - it's the same as accessing property directly
val bindingContext = resolutionFacade.analyze(declaration)
if (!bindingContext[BindingContext.BACKING_FIELD_REQUIRED, property]) return null
val lookupElement = createLookupElement(resolutionFacade, property, true)
return object : LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = "$" + super.getLookupString()
override fun getAllLookupStrings() = setOf(getLookupString())
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText("$" + presentation.getItemText())
presentation.setIcon(PlatformIcons.FIELD_ICON) //TODO: special icon
}
}.assignPriority(ItemPriority.BACKING_FIELD)
}
@@ -16,26 +16,28 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.completion.handlers.*
import com.intellij.codeInsight.completion.PrefixMatcher
import java.util.ArrayList
import com.intellij.codeInsight.completion.CompletionResultSet
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import com.intellij.openapi.util.TextRange
import com.intellij.codeInsight.completion.CompletionParameters
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.handlers.*
import java.util.ArrayList
class LookupElementsCollector(
private val prefixMatcher: PrefixMatcher,
private val completionParameters: CompletionParameters,
private val resolutionFacade: ResolutionFacade,
private val lookupElementFactory: LookupElementFactory,
private val inDescriptor: DeclarationDescriptor?,
private val surroundCallsWithBraces: Boolean
) {
private val elements = ArrayList<LookupElement>()
@@ -114,6 +116,13 @@ class LookupElementsCollector(
}
}
}
if (descriptor is PropertyDescriptor) {
val lookupElement = lookupElementFactory.createBackingFieldLookupElement(descriptor, inDescriptor, resolutionFacade)
if (lookupElement != null) {
addElement(lookupElement)
}
}
}
public fun addElement(element: LookupElement) {
@@ -52,6 +52,7 @@ class SmartCompletion(
val moduleDescriptor: ModuleDescriptor,
val bindingContext: BindingContext,
val visibilityFilter: (DeclarationDescriptor) -> Boolean,
val inDescriptor: DeclarationDescriptor?,
val prefixMatcher: PrefixMatcher,
val inheritorSearchScope: GlobalSearchScope,
val toFromOriginalFileMapper: ToFromOriginalFileMapper,
@@ -141,11 +142,18 @@ class SmartCompletion(
val result = ArrayList<LookupElement>()
val types = descriptor.fuzzyTypes(smartCastTypes)
val classifier = { (expectedInfo: ExpectedInfo) -> types.classifyExpectedInfo(expectedInfo) }
result.addLookupElements(descriptor, expectedInfos, classifier) { descriptor ->
val infoClassifier = { (expectedInfo: ExpectedInfo) -> types.classifyExpectedInfo(expectedInfo) }
result.addLookupElements(descriptor, expectedInfos, infoClassifier) { descriptor ->
lookupElementFactory.createLookupElement(descriptor, resolutionFacade, bindingContext, true)
}
if (descriptor is PropertyDescriptor) {
result.addLookupElements(descriptor, expectedInfos, infoClassifier) { descriptor ->
lookupElementFactory.createBackingFieldLookupElement(descriptor, inDescriptor, resolutionFacade)
}
}
if (receiver == null) {
toFunctionReferenceLookupElement(descriptor, functionExpectedInfos)?.let { result.add(it) }
}
@@ -0,0 +1,26 @@
var globalProperty = "abc" // has backing field and accessor but backing field is not accessible
get() = $globalProperty + 1
class C {
var property1 = "abc" // has backing field but accessors are default - no sense to show in completion
var property2 = "abc" // has backing field but accessors are also default
private set
var property3: String // no backing field
get() = "abc"
set(value){}
var property4 = "abc" // has backing field and accessor
get() = $property4 + 1
fun foo(){
<caret>
}
}
// ABSENT: $globalProperty
// ABSENT: $property1
// ABSENT: $property2
// ABSENT: $property3
// EXIST: { lookupString: "$property4", itemText: "$property4", tailText: null, typeText: "String" }
@@ -0,0 +1,4 @@
var globalProperty = "abc"
get() = <caret>
// EXIST: $globalProperty
@@ -0,0 +1,11 @@
class C {
var property = "abc"
get() = $property + 1
}
fun foo(c: C){
c.<caret>
}
// ABSENT: $property
@@ -0,0 +1,10 @@
class C {
var property = "abc"
get() = $property + 1
fun foo() {
this.<caret>
}
}
// EXIST: $property
@@ -0,0 +1,15 @@
class C {
var property1 = "abc"
get() = $property1 + 1
var property2 = "abc"
get() = $property2 + 1
fun foo(){
$<caret>
}
}
// EXIST: $property1
// EXIST: $property2
// ABSENT: property1
// ABSENT: property2
@@ -0,0 +1,13 @@
class C {
var aProperty = "abc"
get() {}
var bProperty = "abc"
get() {}
fun foo(){
$a<caret>
}
}
// EXIST: $aProperty
// ABSENT: $bProperty
@@ -0,0 +1,15 @@
class C {
var property1 = "abc"
get() = $property1 + 1
var property2 = "abc"
get() = $property2 + 1
fun foo(){
"${$<caret>}"
}
}
// EXIST: $property1
// EXIST: $property2
// ABSENT: property1
// ABSENT: property2
@@ -0,0 +1,27 @@
class C {
var property1 = "abc" // has backing field but accessors are default - no sense to show in completion
var property2: String // no backing field
get() = "abc"
set(value){}
var property3 = "abc" // has backing field and accessor
get() = $property3 + 1
var property4 = 1
get() = $property4 + 1
var property5: String? = null
get() = $property3 + 1
fun foo(): String {
return <caret>
}
}
// ABSENT: $property1
// ABSENT: $property2
// EXIST: $property3
// ABSENT: $property4
// EXIST: { itemText: "!! $property5" }
// EXIST: { itemText: "?: $property5" }
@@ -79,6 +79,48 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/basic/common"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("BackingFields1.kt")
public void testBackingFields1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields1.kt");
doTest(fileName);
}
@TestMetadata("BackingFields2.kt")
public void testBackingFields2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields2.kt");
doTest(fileName);
}
@TestMetadata("BackingFields3.kt")
public void testBackingFields3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields3.kt");
doTest(fileName);
}
@TestMetadata("BackingFields4.kt")
public void testBackingFields4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields4.kt");
doTest(fileName);
}
@TestMetadata("BackingFields5.kt")
public void testBackingFields5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields5.kt");
doTest(fileName);
}
@TestMetadata("BackingFields6.kt")
public void testBackingFields6() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields6.kt");
doTest(fileName);
}
@TestMetadata("BackingFieldsInStringTemplate.kt")
public void testBackingFieldsInStringTemplate() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFieldsInStringTemplate.kt");
doTest(fileName);
}
@TestMetadata("BasicAny.kt")
public void testBasicAny() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BasicAny.kt");
@@ -79,6 +79,48 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/basic/common"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("BackingFields1.kt")
public void testBackingFields1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields1.kt");
doTest(fileName);
}
@TestMetadata("BackingFields2.kt")
public void testBackingFields2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields2.kt");
doTest(fileName);
}
@TestMetadata("BackingFields3.kt")
public void testBackingFields3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields3.kt");
doTest(fileName);
}
@TestMetadata("BackingFields4.kt")
public void testBackingFields4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields4.kt");
doTest(fileName);
}
@TestMetadata("BackingFields5.kt")
public void testBackingFields5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields5.kt");
doTest(fileName);
}
@TestMetadata("BackingFields6.kt")
public void testBackingFields6() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFields6.kt");
doTest(fileName);
}
@TestMetadata("BackingFieldsInStringTemplate.kt")
public void testBackingFieldsInStringTemplate() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BackingFieldsInStringTemplate.kt");
doTest(fileName);
}
@TestMetadata("BasicAny.kt")
public void testBasicAny() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/BasicAny.kt");
@@ -66,6 +66,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName);
}
@TestMetadata("BackingFields.kt")
public void testBackingFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/BackingFields.kt");
doTest(fileName);
}
@TestMetadata("BeforeArgumentWithBinaryOperation.kt")
public void testBeforeArgumentWithBinaryOperation() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/BeforeArgumentWithBinaryOperation.kt");