KT-6079 Show "parameter information" popup for type arguments
#KT-6079 Fixed
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user