KT-5875 Support code completion after "return@"

#KT-5875 Fixed
This commit is contained in:
Valentin Kipyatkov
2014-12-26 18:58:24 +03:00
parent 95285f7a2e
commit cdb5ec3492
15 changed files with 232 additions and 25 deletions
@@ -70,7 +70,7 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
val reference = position.getParent()?.getReferences()?.firstIsInstanceOrNull<JetSimpleNameReference>()
if (reference != null) {
if (reference.expression is JetLabelReferenceExpression) {
this.expression = reference.expression.getParent().getParent() as? JetThisExpression
this.expression = reference.expression.getParent().getParent() as? JetExpressionWithLabel
this.reference = null
}
else {
@@ -242,6 +242,13 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
}
}
// if "return" is parsed correctly in the current context - insert it and all return@xxx items
"return" -> {
if (expression != null) {
collector.addElements(returnExpressionItems(bindingContext!!, expression))
}
}
else -> collector.addElement(lookupElement)
}
}
@@ -58,6 +58,11 @@ import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.plugin.util.FuzzyType
import org.jetbrains.jet.lang.psi.JetLabeledExpression
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.psi.psiUtil.parents
import org.jetbrains.jet.lang.psi.JetReferenceExpression
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
enum class ItemPriority {
MULTIPLE_ARGUMENTS_ITEM
@@ -210,22 +215,17 @@ fun shouldCompleteThisItems(prefixMatcher: PrefixMatcher): Boolean {
data class ThisItemInfo(val factory: () -> LookupElement, val type: FuzzyType)
fun thisExpressionItems(bindingContext: BindingContext, context: JetExpression): Collection<ThisItemInfo> {
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] ?: return listOf()
fun thisExpressionItems(bindingContext: BindingContext, position: JetExpression): Collection<ThisItemInfo> {
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, position] ?: return listOf()
val result = ArrayList<ThisItemInfo>()
for ((i, receiver) in scope.getImplicitReceiversWithInstance().withIndex()) {
val thisType = receiver.getType()
val fuzzyType = FuzzyType(thisType, listOf())
val qualifier = if (i == 0) null else (thisQualifierName(receiver) ?: continue)
val label = if (i == 0) null else (thisQualifierName(receiver) ?: continue)
fun createLookupElement(): LookupElement {
var element = LookupElementBuilder.create(KeywordLookupObject, if (qualifier == null) "this" else "this@" + qualifier)
element = element.withPresentableText("this")
element = element.withBoldness(true)
if (qualifier != null) {
element = element.withTailText("@" + qualifier, false)
}
var element = createKeywordWithLabelElement("this", label)
element = element.withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType))
return element
}
@@ -243,17 +243,71 @@ private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? {
}
val functionLiteral = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? JetFunctionLiteral ?: return null
val literalParent = (functionLiteral.getParent() as JetFunctionLiteralExpression).getParent()
when (literalParent) {
is JetLabeledExpression -> return literalParent.getLabelName()
return functionLiteralLabel(functionLiteral)
}
is JetValueArgument -> {
val parent = literalParent.getParent()
val callExpression = (if (parent is JetValueArgumentList) parent else literalParent)
.getParent() as? JetCallExpression
return (callExpression?.getCalleeExpression() as? JetSimpleNameExpression)?.getReferencedName()
private fun functionLiteralLabel(functionLiteral: JetFunctionLiteral): String?
= functionLiteralLabelAndCall(functionLiteral).first
private fun functionLiteralLabelAndCall(functionLiteral: JetFunctionLiteral): Pair<String?, JetCallExpression?> {
val literalParent = (functionLiteral.getParent() as JetFunctionLiteralExpression).getParent()
fun JetValueArgument.callExpression(): JetCallExpression? {
val parent = getParent()
return (if (parent is JetValueArgumentList) parent else this).getParent() as? JetCallExpression
}
when (literalParent) {
is JetLabeledExpression -> {
val callExpression = (literalParent.getParent() as? JetValueArgument)?.callExpression()
return Pair(literalParent.getLabelName(), callExpression)
}
else -> return null
is JetValueArgument -> {
val callExpression = literalParent.callExpression()
val label = (callExpression?.getCalleeExpression() as? JetSimpleNameExpression)?.getReferencedName()
return Pair(label, callExpression)
}
else -> {
return Pair(null, null)
}
}
}
fun returnExpressionItems(bindingContext: BindingContext, position: JetElement): Collection<LookupElement> {
val result = ArrayList<LookupElement>()
for (parent in position.parents()) {
when (parent) {
is JetFunctionLiteral -> {
val (label, call) = functionLiteralLabelAndCall(parent)
if (label != null) {
result.add(createKeywordWithLabelElement("return", label))
}
// check if the current function literal is inlined and stop processing outer declarations if it's not
val callee = call?.getCalleeExpression() as? JetReferenceExpression ?: break // not inlined
val target = bindingContext[BindingContext.REFERENCE_TARGET, callee] as? SimpleFunctionDescriptor ?: break // not inlined
if (!target.getInlineStrategy().isInline()) break // not inlined
}
is JetDeclarationWithBody -> {
if (parent.hasBlockBody()) {
result.add(createKeywordWithLabelElement("return", null))
}
break
}
}
}
return result
}
private fun createKeywordWithLabelElement(keyword: String, label: String?): LookupElementBuilder {
var element = LookupElementBuilder.create(KeywordLookupObject, if (label == null) keyword else "$keyword@$label")
element = element.withPresentableText(keyword)
element = element.withBoldness(true)
if (label != null) {
element = element.withTailText("@$label", false)
}
return element
}
@@ -9,11 +9,10 @@ fun foo() = <caret>
// EXIST: null
// EXIST: object
// EXIST: package
// EXIST: return
// EXIST: super
// EXIST: throw
// EXIST: true
// EXIST: try
// EXIST: when
// EXIST: while
// NUMBER: 16
// NUMBER: 15
@@ -10,11 +10,10 @@ val prop: Int
// EXIST: null
// EXIST: object
// EXIST: package
// EXIST: return
// EXIST: super
// EXIST: throw
// EXIST: true
// EXIST: try
// EXIST: when
// EXIST: while
// NUMBER: 16
// NUMBER: 15
@@ -9,11 +9,10 @@ var a : Int = <caret>
// EXIST: null
// EXIST: object
// EXIST: package
// EXIST: return
// EXIST: super
// EXIST: throw
// EXIST: true
// EXIST: try
// EXIST: when
// EXIST: while
// NUMBER: 16
// NUMBER: 15
@@ -0,0 +1,14 @@
fun foo() {
takeHandler1 {
takeHandler2({ ret<caret> })
}
}
inline fun takeHandler1(handler: () -> Unit){}
inline fun takeHandler2(handler: () -> Unit){}
// INVOCATION_COUNT: 1
// EXIST: { lookupString: "return", itemText: "return", tailText: null, attributes: "bold" }
// EXIST: { lookupString: "return@takeHandler1", itemText: "return", tailText: "@takeHandler1", attributes: "bold" }
// EXIST: { lookupString: "return@takeHandler2", itemText: "return", tailText: "@takeHandler2", attributes: "bold" }
// NUMBER: 3
@@ -0,0 +1,13 @@
fun foo() {
takeHandler1 {
takeHandler2 { <caret> }
}
}
fun takeHandler1(handler: () -> Unit){}
inline fun takeHandler2(handler: () -> Unit){}
// INVOCATION_COUNT: 1
// ABSENT: return
// EXIST: { lookupString: "return@takeHandler1", itemText: "return", tailText: "@takeHandler1", attributes: "bold" }
// EXIST: { lookupString: "return@takeHandler2", itemText: "return", tailText: "@takeHandler2", attributes: "bold" }
@@ -0,0 +1,13 @@
fun foo() {
takeHandler1 {
takeHandler2 { <caret> }
}
}
fun takeHandler1(handler: () -> Unit){}
fun takeHandler2(handler: () -> Unit){}
// INVOCATION_COUNT: 1
// ABSENT: return
// ABSENT: return@takeHandler1
// EXIST: { lookupString: "return@takeHandler2", itemText: "return", tailText: "@takeHandler2", attributes: "bold" }
@@ -0,0 +1,12 @@
fun foo() {
takeHandler @label {
<caret>
}
}
fun takeHandler(handler: () -> Unit){}
// INVOCATION_COUNT: 1
// ABSENT: return
// ABSENT: "return@takeHandler"
// EXIST: { lookupString: "return@label", itemText: "return", tailText: "@label", attributes: "bold" }
@@ -0,0 +1,12 @@
fun foo() {
takeHandler @label {
<caret>
}
}
inline fun takeHandler(handler: () -> Unit){}
// INVOCATION_COUNT: 1
// EXIST: { lookupString: "return", itemText: "return", tailText: null, attributes: "bold" }
// ABSENT: "return@takeHandler"
// EXIST: { lookupString: "return@label", itemText: "return", tailText: "@label", attributes: "bold" }
@@ -0,0 +1,13 @@
fun foo() {
takeHandler1 {
takeHandler2({ return@<caret> })
}
}
inline fun takeHandler1(handler: () -> Unit){}
inline fun takeHandler2(handler: () -> Unit){}
// INVOCATION_COUNT: 1
// EXIST: { lookupString: "return@takeHandler1", itemText: "return", tailText: "@takeHandler1", attributes: "bold" }
// EXIST: { lookupString: "return@takeHandler2", itemText: "return", tailText: "@takeHandler2", attributes: "bold" }
// NUMBER: 2
@@ -0,0 +1,6 @@
val property: Int
get() {
<caret>
}
// EXIST: return
@@ -0,0 +1,3 @@
fun foo(p: Int?): Int = p ?: <caret>
// ABSENT: return
@@ -0,0 +1,9 @@
inline fun run (p: () -> Unit) {}
fun foo() = run {
<caret>
}
// INVOCATION_COUNT: 1
// EXIST: { lookupString: "return@run", itemText: "return", tailText: "@run", attributes: "bold" }
// ABSENT: return
@@ -287,6 +287,60 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes
doTest(fileName);
}
@TestMetadata("Return1.kt")
public void testReturn1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return1.kt");
doTest(fileName);
}
@TestMetadata("Return2.kt")
public void testReturn2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return2.kt");
doTest(fileName);
}
@TestMetadata("Return3.kt")
public void testReturn3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return3.kt");
doTest(fileName);
}
@TestMetadata("Return4.kt")
public void testReturn4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return4.kt");
doTest(fileName);
}
@TestMetadata("Return5.kt")
public void testReturn5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return5.kt");
doTest(fileName);
}
@TestMetadata("Return6.kt")
public void testReturn6() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return6.kt");
doTest(fileName);
}
@TestMetadata("Return7.kt")
public void testReturn7() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return7.kt");
doTest(fileName);
}
@TestMetadata("Return8.kt")
public void testReturn8() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return8.kt");
doTest(fileName);
}
@TestMetadata("Return9.kt")
public void testReturn9() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/Return9.kt");
doTest(fileName);
}
@TestMetadata("This.kt")
public void testThis() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/keywords/This.kt");