Add inlay hints for values returned from lambdas

#KT-20067 Fixed
This commit is contained in:
Dmitry Jemerov
2018-01-04 12:42:32 +01:00
parent e558837214
commit da157fafdc
4 changed files with 196 additions and 51 deletions

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2010-2017 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.
* 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.cfg

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2010-2017 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.
* 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
@@ -30,6 +19,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getRet
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
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.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
@@ -51,9 +41,10 @@ enum class HintType(desc: String, enabled: Boolean) {
return providePropertyTypeHint(elem)
}
override fun isApplicable(elem: PsiElement): Boolean = (elem is KtProperty && elem.getReturnTypeReference() == null && elem.isLocal) ||
(elem is KtParameter && elem.isLoopParameter && elem.typeReference == null) ||
(elem is KtDestructuringDeclarationEntry && elem.getReturnTypeReference() == null)
override fun isApplicable(elem: PsiElement): Boolean =
(elem is KtProperty && elem.getReturnTypeReference() == null && elem.isLocal) ||
(elem is KtParameter && elem.isLoopParameter && elem.typeReference == null) ||
(elem is KtDestructuringDeclarationEntry && elem.getReturnTypeReference() == null)
},
FUNCTION_HINT("Show function return type hints", false) {
@@ -66,8 +57,10 @@ enum class HintType(desc: String, enabled: Boolean) {
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) {
override fun provideHints(elem: PsiElement): List<InlayInfo> {
(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
},
PARAMETER_HINT("Show argument name hints", true) {
override fun provideHints(elem: PsiElement): List<InlayInfo> {
(elem as? KtCallElement)?.let {
return provideArgumentNameHints(it)
val callElement = elem.getStrictParentOfType<KtCallElement>() ?: return emptyList()
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()
}
override fun isApplicable(elem: PsiElement): Boolean = elem is KtCallElement
};
}
;
companion object {
@@ -99,8 +103,7 @@ enum class HintType(desc: String, enabled: Boolean) {
val resolved = elem?.let { resolve(it) } ?: return null
return if (resolved.enabled) {
resolved
}
else {
} else {
null
}
}
@@ -118,26 +121,30 @@ class KotlinInlayParameterHintsProvider : InlayParameterHintsProvider {
override fun getSupportedOptions(): List<Option> = HintType.values().map { it.option }
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? {
val hintType = HintType.resolve(element) ?: return null
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)
}
}
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 getInlayPresentation(inlayText: String): String = if (inlayText.startsWith(TYPE_INFO_PREFIX)) {
inlayText.substring(TYPE_INFO_PREFIX.length)
}
else {
super.getInlayPresentation(inlayText)
}
override fun getInlayPresentation(inlayText: String): String =
if (inlayText.startsWith(TYPE_INFO_PREFIX)) {
inlayText.substring(TYPE_INFO_PREFIX.length)
} else {
super.getInlayPresentation(inlayText)
}
private fun getMethodInfo(elem: KtCallElement): HintInfo.MethodInfo? {
val ctx = elem.analyze(BodyResolveMode.PARTIAL)
@@ -149,11 +156,11 @@ class KotlinInlayParameterHintsProvider : InlayParameterHintsProvider {
else
(resolvedCallee.fqNameOrNull()?.asString() ?: return null)
val paramNames = resolvedCall.valueArguments
.mapNotNull { (valueParameterDescriptor, resolvedValueArgument) ->
if (resolvedValueArgument !is DefaultValueArgument) valueParameterDescriptor.name else null
}
.filter { !it.isSpecial }
.map(Name::asString)
.mapNotNull { (valueParameterDescriptor, resolvedValueArgument) ->
if (resolvedValueArgument !is DefaultValueArgument) valueParameterDescriptor.name else null
}
.filter { !it.isSpecial }
.map(Name::asString)
return HintInfo.MethodInfo(fqName, paramNames)
}
return null

View File

@@ -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
}

View File

@@ -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
}
}
}"""
)
}
}