Add inlay hints for values returned from lambdas
#KT-20067 Fixed
This commit is contained in:
@@ -1,17 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2017 JetBrains s.r.o.
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
*
|
* that can be found in the license/LICENSE.txt file.
|
||||||
* 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.cfg
|
package org.jetbrains.kotlin.cfg
|
||||||
|
|||||||
+45
-38
@@ -1,17 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2017 JetBrains s.r.o.
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
*
|
* that can be found in the license/LICENSE.txt file.
|
||||||
* 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
|
package org.jetbrains.kotlin.idea.parameterInfo
|
||||||
@@ -30,6 +19,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getRet
|
|||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||||
@@ -51,9 +41,10 @@ enum class HintType(desc: String, enabled: Boolean) {
|
|||||||
return providePropertyTypeHint(elem)
|
return providePropertyTypeHint(elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isApplicable(elem: PsiElement): Boolean = (elem is KtProperty && elem.getReturnTypeReference() == null && elem.isLocal) ||
|
override fun isApplicable(elem: PsiElement): Boolean =
|
||||||
(elem is KtParameter && elem.isLoopParameter && elem.typeReference == null) ||
|
(elem is KtProperty && elem.getReturnTypeReference() == null && elem.isLocal) ||
|
||||||
(elem is KtDestructuringDeclarationEntry && elem.getReturnTypeReference() == null)
|
(elem is KtParameter && elem.isLoopParameter && elem.typeReference == null) ||
|
||||||
|
(elem is KtDestructuringDeclarationEntry && elem.getReturnTypeReference() == null)
|
||||||
},
|
},
|
||||||
|
|
||||||
FUNCTION_HINT("Show function return type hints", false) {
|
FUNCTION_HINT("Show function return type hints", false) {
|
||||||
@@ -66,8 +57,10 @@ enum class HintType(desc: String, enabled: Boolean) {
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isApplicable(elem: PsiElement): Boolean = elem is KtNamedFunction && !(elem.hasBlockBody() || elem.hasDeclaredReturnType())
|
override fun isApplicable(elem: PsiElement): Boolean =
|
||||||
|
elem is KtNamedFunction && !(elem.hasBlockBody() || elem.hasDeclaredReturnType())
|
||||||
},
|
},
|
||||||
|
|
||||||
PARAMETER_TYPE_HINT("Show parameter type hints ", false) {
|
PARAMETER_TYPE_HINT("Show parameter type hints ", false) {
|
||||||
override fun provideHints(elem: PsiElement): List<InlayInfo> {
|
override fun provideHints(elem: PsiElement): List<InlayInfo> {
|
||||||
(elem as? KtParameter)?.let { param ->
|
(elem as? KtParameter)?.let { param ->
|
||||||
@@ -80,16 +73,27 @@ enum class HintType(desc: String, enabled: Boolean) {
|
|||||||
|
|
||||||
override fun isApplicable(elem: PsiElement): Boolean = elem is KtParameter && elem.typeReference == null && !elem.isLoopParameter
|
override fun isApplicable(elem: PsiElement): Boolean = elem is KtParameter && elem.typeReference == null && !elem.isLoopParameter
|
||||||
},
|
},
|
||||||
|
|
||||||
PARAMETER_HINT("Show argument name hints", true) {
|
PARAMETER_HINT("Show argument name hints", true) {
|
||||||
override fun provideHints(elem: PsiElement): List<InlayInfo> {
|
override fun provideHints(elem: PsiElement): List<InlayInfo> {
|
||||||
(elem as? KtCallElement)?.let {
|
val callElement = elem.getStrictParentOfType<KtCallElement>() ?: return emptyList()
|
||||||
return provideArgumentNameHints(it)
|
return provideArgumentNameHints(callElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isApplicable(elem: PsiElement): Boolean = elem is KtValueArgumentList
|
||||||
|
},
|
||||||
|
|
||||||
|
LAMBDA_RETURN_EXPRESSION("Show lambda return expression hints", true) {
|
||||||
|
override fun isApplicable(elem: PsiElement) = elem is KtExpression
|
||||||
|
|
||||||
|
override fun provideHints(elem: PsiElement): List<InlayInfo> {
|
||||||
|
if (elem is KtExpression) {
|
||||||
|
return provideLambdaReturnValueHints(elem)
|
||||||
}
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
override fun isApplicable(elem: PsiElement): Boolean = elem is KtCallElement
|
;
|
||||||
};
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@@ -99,8 +103,7 @@ enum class HintType(desc: String, enabled: Boolean) {
|
|||||||
val resolved = elem?.let { resolve(it) } ?: return null
|
val resolved = elem?.let { resolve(it) } ?: return null
|
||||||
return if (resolved.enabled) {
|
return if (resolved.enabled) {
|
||||||
resolved
|
resolved
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,26 +121,30 @@ class KotlinInlayParameterHintsProvider : InlayParameterHintsProvider {
|
|||||||
override fun getSupportedOptions(): List<Option> = HintType.values().map { it.option }
|
override fun getSupportedOptions(): List<Option> = HintType.values().map { it.option }
|
||||||
|
|
||||||
override fun getDefaultBlackList(): Set<String> =
|
override fun getDefaultBlackList(): Set<String> =
|
||||||
setOf("*listOf", "*setOf", "*arrayOf", "*ListOf", "*SetOf", "*ArrayOf", "*assert*(*)", "*mapOf", "*MapOf")
|
setOf("*listOf", "*setOf", "*arrayOf", "*ListOf", "*SetOf", "*ArrayOf", "*assert*(*)", "*mapOf", "*MapOf")
|
||||||
|
|
||||||
override fun getHintInfo(element: PsiElement): HintInfo? {
|
override fun getHintInfo(element: PsiElement): HintInfo? {
|
||||||
val hintType = HintType.resolve(element) ?: return null
|
val hintType = HintType.resolve(element) ?: return null
|
||||||
return when (hintType) {
|
return when (hintType) {
|
||||||
HintType.PARAMETER_HINT -> (element as? KtCallElement)?.let { getMethodInfo(it) }
|
HintType.PARAMETER_HINT -> {
|
||||||
|
val parent = (element as? KtValueArgumentList)?.parent
|
||||||
|
(parent as? KtCallElement)?.let { getMethodInfo(it) }
|
||||||
|
}
|
||||||
else -> HintInfo.OptionInfo(hintType.option)
|
else -> HintInfo.OptionInfo(hintType.option)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getParameterHints(element: PsiElement?): List<InlayInfo> = HintType.resolveToEnabled(element)?.provideHints(element!!) ?: emptyList()
|
override fun getParameterHints(element: PsiElement?): List<InlayInfo> =
|
||||||
|
HintType.resolveToEnabled(element)?.provideHints(element!!) ?: emptyList()
|
||||||
|
|
||||||
override fun getBlackListDependencyLanguage(): Language = JavaLanguage.INSTANCE
|
override fun getBlackListDependencyLanguage(): Language = JavaLanguage.INSTANCE
|
||||||
|
|
||||||
override fun getInlayPresentation(inlayText: String): String = if (inlayText.startsWith(TYPE_INFO_PREFIX)) {
|
override fun getInlayPresentation(inlayText: String): String =
|
||||||
inlayText.substring(TYPE_INFO_PREFIX.length)
|
if (inlayText.startsWith(TYPE_INFO_PREFIX)) {
|
||||||
}
|
inlayText.substring(TYPE_INFO_PREFIX.length)
|
||||||
else {
|
} else {
|
||||||
super.getInlayPresentation(inlayText)
|
super.getInlayPresentation(inlayText)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMethodInfo(elem: KtCallElement): HintInfo.MethodInfo? {
|
private fun getMethodInfo(elem: KtCallElement): HintInfo.MethodInfo? {
|
||||||
val ctx = elem.analyze(BodyResolveMode.PARTIAL)
|
val ctx = elem.analyze(BodyResolveMode.PARTIAL)
|
||||||
@@ -149,11 +156,11 @@ class KotlinInlayParameterHintsProvider : InlayParameterHintsProvider {
|
|||||||
else
|
else
|
||||||
(resolvedCallee.fqNameOrNull()?.asString() ?: return null)
|
(resolvedCallee.fqNameOrNull()?.asString() ?: return null)
|
||||||
val paramNames = resolvedCall.valueArguments
|
val paramNames = resolvedCall.valueArguments
|
||||||
.mapNotNull { (valueParameterDescriptor, resolvedValueArgument) ->
|
.mapNotNull { (valueParameterDescriptor, resolvedValueArgument) ->
|
||||||
if (resolvedValueArgument !is DefaultValueArgument) valueParameterDescriptor.name else null
|
if (resolvedValueArgument !is DefaultValueArgument) valueParameterDescriptor.name else null
|
||||||
}
|
}
|
||||||
.filter { !it.isSpecial }
|
.filter { !it.isSpecial }
|
||||||
.map(Name::asString)
|
.map(Name::asString)
|
||||||
return HintInfo.MethodInfo(fqName, paramNames)
|
return HintInfo.MethodInfo(fqName, paramNames)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.parameterInfo
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.hints.InlayInfo
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsResultOfLambda
|
||||||
|
|
||||||
|
fun provideLambdaReturnValueHints(expression: KtExpression): List<InlayInfo> {
|
||||||
|
if (expression is KtIfExpression || expression is KtWhenExpression || expression is KtBlockExpression) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expression.parent is KtDotQualifiedExpression || expression.parent is KtSafeQualifiedExpression) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val functionLiteral = expression.getParentOfType<KtFunctionLiteral>(true)
|
||||||
|
val body = functionLiteral?.bodyExpression ?: return emptyList()
|
||||||
|
if (body.statements.size == 1 && body.statements[0] == expression) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val bindingContext = expression.analyze()
|
||||||
|
if (expression.isUsedAsResultOfLambda(bindingContext)) {
|
||||||
|
val lambdaName = getNameOfFunctionThatTakesLambda(expression) ?: "lambda"
|
||||||
|
return listOf(InlayInfo("$TYPE_INFO_PREFIX^$lambdaName", expression.startOffset))
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNameOfFunctionThatTakesLambda(expression: KtExpression): String? {
|
||||||
|
val lambda = expression.getStrictParentOfType<KtLambdaExpression>() ?: return null
|
||||||
|
val callExpression = lambda.getStrictParentOfType<KtCallExpression>() ?: return null
|
||||||
|
if (callExpression.lambdaArguments.any { it.getLambdaExpression() == lambda }) {
|
||||||
|
val parent = lambda.parent
|
||||||
|
if (parent is KtLabeledExpression) {
|
||||||
|
return parent.getLabelName()
|
||||||
|
}
|
||||||
|
return (callExpression.calleeExpression as? KtNameReferenceExpression)?.getReferencedName()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.parameterInfo
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||||
|
|
||||||
|
class LambdaReturnValueHintsTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
|
override fun getProjectDescriptor(): KotlinLightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||||
|
|
||||||
|
fun check(text: String) {
|
||||||
|
myFixture.configureByText("A.kt", text)
|
||||||
|
myFixture.testInlays()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testSimple() {
|
||||||
|
check(
|
||||||
|
"""val x = run {
|
||||||
|
println("foo")
|
||||||
|
<hint text="^run" />1
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testQualified() {
|
||||||
|
check(
|
||||||
|
"""val x = run {
|
||||||
|
var s = "abc"
|
||||||
|
<hint text="^run" />s.length
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testIf() {
|
||||||
|
check(
|
||||||
|
"""val x = run {
|
||||||
|
if (true) {
|
||||||
|
<hint text="^run" />1
|
||||||
|
} else {
|
||||||
|
<hint text="^run" />0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testWhen() {
|
||||||
|
check(
|
||||||
|
"""val x = run {
|
||||||
|
when (true) {
|
||||||
|
true -> <hint text="^run" />1
|
||||||
|
false -><hint text="^run" />0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testNoHintForSingleExpression() {
|
||||||
|
check(
|
||||||
|
"""val x = run {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testLabel() {
|
||||||
|
check(
|
||||||
|
"""val x = run foo@{
|
||||||
|
println("foo")
|
||||||
|
<hint text="^foo" />1
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testNested() {
|
||||||
|
check(
|
||||||
|
"""val x = run hello@{
|
||||||
|
if (true) {
|
||||||
|
}
|
||||||
|
|
||||||
|
<hint text="^hello" />run { // Two hints here
|
||||||
|
when (true) {
|
||||||
|
true -> <hint text="^run" />1
|
||||||
|
false -> <hint text="^run" />0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user