KT-6079 Show "parameter information" popup for type arguments

#KT-6079 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-01-20 13:12:44 +03:00
parent bf16783fb7
commit 84e530bc1a
14 changed files with 331 additions and 3 deletions
@@ -52,6 +52,7 @@ fun KotlinType.isNothing(): Boolean = KotlinBuiltIns.isNothing(this)
fun KotlinType.isNullableNothing(): Boolean = KotlinBuiltIns.isNullableNothing(this)
fun KotlinType.isUnit(): Boolean = KotlinBuiltIns.isUnit(this)
fun KotlinType.isAnyOrNullableAny(): Boolean = KotlinBuiltIns.isAnyOrNullableAny(this)
fun KotlinType.isNullableAny(): Boolean = KotlinBuiltIns.isNullableAny(this)
fun KotlinType.isBoolean(): Boolean = KotlinBuiltIns.isBoolean(this)
fun KotlinType.isBooleanOrNullableBoolean(): Boolean = KotlinBuiltIns.isBooleanOrNullableBoolean(this)
+2
View File
@@ -364,6 +364,8 @@
<codeInsight.parameterInfo language="kotlin" implementationClass="org.jetbrains.kotlin.idea.parameterInfo.KotlinFunctionParameterInfoHandler"/>
<codeInsight.parameterInfo language="kotlin" implementationClass="org.jetbrains.kotlin.idea.parameterInfo.KotlinArrayAccessParameterInfoHandler"/>
<codeInsight.parameterInfo language="kotlin" implementationClass="org.jetbrains.kotlin.idea.parameterInfo.KotlinClassTypeArgumentInfoHandler"/>
<codeInsight.parameterInfo language="kotlin" implementationClass="org.jetbrains.kotlin.idea.parameterInfo.KotlinFunctionTypeArgumentInfoHandler"/>
<codeInsight.gotoSuper language="kotlin" implementationClass="org.jetbrains.kotlin.idea.codeInsight.GotoSuperActionHandler"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>
@@ -75,6 +75,7 @@ public class KotlinTypedHandler extends TypedHandlerDelegate {
case '<':
kotlinLTTyped = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
LtGtTypingUtils.shouldAutoCloseAngleBracket(editor.getCaretModel().getOffset(), editor);
autoPopupParameterInfo(project, editor);
return Result.CONTINUE;
case '>':
@@ -0,0 +1,217 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.parameterInfo
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.lang.parameterInfo.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.resolveCandidates
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
import org.jetbrains.kotlin.types.typeUtil.nullability
class KotlinClassTypeArgumentInfoHandler : KotlinTypeArgumentInfoHandlerBase<ClassDescriptor>() {
override fun fetchTypeParameters(descriptor: ClassDescriptor) = descriptor.typeConstructor.parameters
override fun findParameterOwners(argumentList: KtTypeArgumentList): Collection<ClassDescriptor>? {
val userType = argumentList.parent as? KtUserType ?: return null
val bindingContext = argumentList.analyze(BodyResolveMode.PARTIAL)
return userType.referenceExpression?.mainReference?.resolveToDescriptors(bindingContext)?.mapNotNull { it as? ClassDescriptor }
}
override fun getArgumentListAllowedParentClasses() = setOf(KtUserType::class.java)
}
class KotlinFunctionTypeArgumentInfoHandler : KotlinTypeArgumentInfoHandlerBase<FunctionDescriptor>() {
override fun fetchTypeParameters(descriptor: FunctionDescriptor) = descriptor.typeParameters
override fun findParameterOwners(argumentList: KtTypeArgumentList): Collection<FunctionDescriptor>? {
val callElement = argumentList.parent as? KtCallElement ?: return null
val bindingContext = argumentList.analyze(BodyResolveMode.PARTIAL)
val call = callElement.getCall(bindingContext) ?: return null
val candidates = call.resolveCandidates(bindingContext, callElement.getResolutionFacade())
return candidates
.map { it.resultingDescriptor }
.distinctBy { buildPresentation(it.typeParameters, -1).first }
}
override fun getArgumentListAllowedParentClasses() = setOf(KtCallElement::class.java)
}
abstract class KotlinTypeArgumentInfoHandlerBase<TParameterOwner : DeclarationDescriptor> : ParameterInfoHandlerWithTabActionSupport<KtTypeArgumentList, TParameterOwner, KtTypeProjection> {
protected abstract fun fetchTypeParameters(descriptor: TParameterOwner): List<TypeParameterDescriptor>
protected abstract fun findParameterOwners(argumentList: KtTypeArgumentList): Collection<TParameterOwner>?
override fun getActualParameterDelimiterType() = KtTokens.COMMA
override fun getActualParametersRBraceType() = KtTokens.GT
override fun getArgumentListClass() = KtTypeArgumentList::class.java
override fun getActualParameters(o: KtTypeArgumentList) = o.arguments.toTypedArray()
override fun getArgListStopSearchClasses() = setOf(KtNamedFunction::class.java, KtVariableDeclaration::class.java, KtClassOrObject::class.java)
override fun getParameterCloseChars() = ParameterInfoUtils.DEFAULT_PARAMETER_CLOSE_CHARS
override fun tracksParameterIndex() = true
override fun couldShowInLookup() = false
override fun getParametersForLookup(item: LookupElement?, context: ParameterInfoContext?) = emptyArray<Any>()
override fun getParametersForDocumentation(p: TParameterOwner, context: ParameterInfoContext?) = emptyArray<Any>()
override fun showParameterInfo(element: KtTypeArgumentList, context: CreateParameterInfoContext) {
context.showHint(element, element.textRange.startOffset, this)
}
override fun findElementForUpdatingParameterInfo(context: UpdateParameterInfoContext): KtTypeArgumentList? {
val element = context.file.findElementAt(context.offset) ?: return null
val argumentList = element.getParentOfType<KtTypeArgumentList>(true) ?: return null
val argument = element.parents.takeWhile { it != argumentList }.lastOrNull() as? KtTypeProjection
if (argument != null) {
val arguments = getActualParameters(argumentList)
val index = arguments.indexOf(element)
context.setCurrentParameter(index)
context.setHighlightedParameter(element)
}
return argumentList
}
override fun findElementForParameterInfo(context: CreateParameterInfoContext): KtTypeArgumentList? {
val file = context.file as? KtFile ?: return null
val token = file.findElementAt(context.offset) ?: return null
val argumentList = token.getParentOfType<KtTypeArgumentList>(true) ?: return null
val parameterOwners = findParameterOwners(argumentList) ?: return null
context.itemsToShow = parameterOwners.toTypedArray<Any>()
return argumentList
}
override fun updateParameterInfo(argumentList: KtTypeArgumentList, context: UpdateParameterInfoContext) {
if (context.parameterOwner !== argumentList) {
context.removeHint()
}
val offset = context.offset
val parameterIndex = argumentList.allChildren
.takeWhile { it.startOffset < offset }
.count { it.node.elementType == KtTokens.COMMA }
context.setCurrentParameter(parameterIndex)
}
override fun updateUI(itemToShow: TParameterOwner, context: ParameterInfoUIContext) {
if (!updateUIOrFail(itemToShow, context)) {
context.isUIComponentEnabled = false
return
}
}
private fun updateUIOrFail(itemToShow: TParameterOwner, context: ParameterInfoUIContext): Boolean {
val currentIndex = context.currentParameterIndex
if (currentIndex < 0) return false // by some strange reason we are invoked with currentParameterIndex == -1 during initialization
val parameters = fetchTypeParameters(itemToShow)
val (text, currentParameterStart, currentParameterEnd) = buildPresentation(parameters, currentIndex)
context.setupUIComponentPresentation(text, currentParameterStart, currentParameterEnd,
false/*isDisabled*/, false/*strikeout*/, false/*isDisabledBeforeHighlight*/,
context.defaultParameterColor)
return true
}
protected fun buildPresentation(
parameters: List<TypeParameterDescriptor>,
currentIndex: Int
): Triple<String, Int, Int> {
var currentParameterStart = -1
var currentParameterEnd = -1
val text = buildString {
var needWhere = false
for ((index, parameter) in parameters.withIndex()) {
if (index > 0) append(", ")
if (index == currentIndex) {
currentParameterStart = length
}
if (parameter.isReified) {
append("reified ")
}
when (parameter.variance) {
Variance.INVARIANT -> {}
Variance.IN_VARIANCE -> append("in ")
Variance.OUT_VARIANCE -> append("out ")
}
append(parameter.name.asString())
val upperBounds = parameter.upperBounds
if (upperBounds.size == 1) {
val upperBound = upperBounds.single()
if (!upperBound.isAnyOrNullableAny() || upperBound.nullability() == TypeNullability.NOT_NULL) { // skip Any? or Any!
append(" : ").append(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(upperBound))
}
}
else if (upperBounds.size > 1) {
needWhere = true
}
if (index == currentIndex) {
currentParameterEnd = length
}
}
if (needWhere) {
append(" where ")
var needComma = false
for (parameter in parameters) {
val upperBounds = parameter.upperBounds
if (upperBounds.size > 1) {
for (upperBound in upperBounds) {
if (needComma) append(", ")
needComma = true
append(parameter.name.asString()).append(" : ").append(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(upperBound))
}
}
}
}
}
return Triple(text, currentParameterStart, currentParameterEnd)
}
}
@@ -0,0 +1,4 @@
open class Base<out T, in V>
class X : Base<<caret>>
//Text: (<highlight>out T</highlight>, in V), Disabled: false, Strikeout: false, Green: false
@@ -0,0 +1,5 @@
open class A<T1 : Any, T2, T3 : List<T1>> where T2 : Runnable, T2 : Cloneable, T3 : Runnable
val v: A<X, <caret>>
//Text: (T1 : Any, <highlight>T2</highlight>, T3 where T2 : Runnable, T2 : Cloneable, T3 : List<T1>, T3 : Runnable), Disabled: false, Strikeout: false, Green: false
@@ -0,0 +1,5 @@
open class Base<out T, in V>
val v = Base<<caret>>
//Text: (<highlight>out T</highlight>, in V), Disabled: false, Strikeout: false, Green: false
@@ -0,0 +1,7 @@
fun <T, V> foo(t: T): T = t
fun bar() {
foo<<caret>>()
}
//Text: (<highlight>T</highlight>, V), Disabled: false, Strikeout: false, Green: false
@@ -0,0 +1,7 @@
import java.util.*
fun foo() {
ArrayList<<caret>>
}
//Text: (<highlight>E</highlight>), Disabled: false, Strikeout: false, Green: false
+12
View File
@@ -0,0 +1,12 @@
fun <T, V> foo(t: T): T = t
fun <T, V> foo(t: T, p: Int): T = t
fun <X> foo(p: Boolean, x: X) {}
fun bar() {
foo<<caret>>()
}
/*
Text: (<highlight>T</highlight>, V), Disabled: false, Strikeout: false, Green: false
Text: (<highlight>X</highlight>), Disabled: false, Strikeout: false, Green: false
*/
+7
View File
@@ -0,0 +1,7 @@
inline fun <reified T> foo(p: T) {}
fun bar() {
foo<<caret>>
}
//Text: (<highlight>reified T</highlight>), Disabled: false, Strikeout: false, Green: false
@@ -0,0 +1,5 @@
open class A<T, V>
val v: A<String, <caret>>
//Text: (T, <highlight>V</highlight>), Disabled: false, Strikeout: false, Green: false
@@ -22,7 +22,6 @@ import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.test.ProjectDescriptorWithStdlibSources
@@ -77,8 +76,7 @@ abstract class AbstractParameterInfoTest : LightCodeInsightFixtureTestCase() {
val parameterInfoUIContext = MockParameterInfoUIContext(parameterOwner, updateContext.currentParameter)
for (item in mockCreateParameterInfoContext.itemsToShow) {
//noinspection unchecked
handler.updateUI(item as FunctionDescriptor, parameterInfoUIContext)
handler.updateUI(item, parameterInfoUIContext)
}
Assert.assertEquals(expectedResultText, parameterInfoUIContext.resultText)
}
@@ -304,4 +304,61 @@ public class ParameterInfoTestGenerated extends AbstractParameterInfoTest {
doTest(fileName);
}
}
@TestMetadata("idea/testData/parameterInfo/typeArguments")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class TypeArguments extends AbstractParameterInfoTest {
public void testAllFilesPresentInTypeArguments() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/parameterInfo/typeArguments"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("BaseClass.kt")
public void testBaseClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/BaseClass.kt");
doTest(fileName);
}
@TestMetadata("Constraints.kt")
public void testConstraints() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/Constraints.kt");
doTest(fileName);
}
@TestMetadata("ConstructorCall.kt")
public void testConstructorCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/ConstructorCall.kt");
doTest(fileName);
}
@TestMetadata("FunctionCall.kt")
public void testFunctionCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/FunctionCall.kt");
doTest(fileName);
}
@TestMetadata("JavaClass.kt")
public void testJavaClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/JavaClass.kt");
doTest(fileName);
}
@TestMetadata("Overloads.kt")
public void testOverloads() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/Overloads.kt");
doTest(fileName);
}
@TestMetadata("Reified.kt")
public void testReified() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/Reified.kt");
doTest(fileName);
}
@TestMetadata("VariableType.kt")
public void testVariableType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/parameterInfo/typeArguments/VariableType.kt");
doTest(fileName);
}
}
}