From 02df3c652d5dd49d43f969a47176da125eca9449 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Tue, 26 Sep 2023 20:39:53 +0100 Subject: [PATCH 01/21] add inlay hint support --- .../org/javacs/kt/KotlinLanguageServer.kt | 1 + .../javacs/kt/KotlinTextDocumentService.kt | 17 ++-- .../org/javacs/kt/inlayhints/InlayHint.kt | 82 +++++++++++++++++++ 3 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index 1c94d2a4d..f243f7977 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -77,6 +77,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable { serverCapabilities.workspace.workspaceFolders = WorkspaceFoldersOptions() serverCapabilities.workspace.workspaceFolders.supported = true serverCapabilities.workspace.workspaceFolders.changeNotifications = Either.forRight(true) + serverCapabilities.inlayHintProvider = Either.forLeft(true) serverCapabilities.hoverProvider = Either.forLeft(true) serverCapabilities.renameProvider = Either.forLeft(true) serverCapabilities.completionProvider = CompletionOptions(false, listOf(".")) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt index d5180a9f8..25153e86f 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt @@ -16,17 +16,11 @@ import org.javacs.kt.position.position import org.javacs.kt.references.findReferences import org.javacs.kt.semantictokens.encodedSemanticTokens import org.javacs.kt.signaturehelp.fetchSignatureHelpAt -import org.javacs.kt.symbols.documentSymbols -import org.javacs.kt.util.noResult -import org.javacs.kt.util.AsyncExecutor -import org.javacs.kt.util.Debouncer -import org.javacs.kt.util.filePath -import org.javacs.kt.util.TemporaryDirectory -import org.javacs.kt.util.parseURI -import org.javacs.kt.util.describeURI -import org.javacs.kt.util.describeURIs import org.javacs.kt.rename.renameSymbol import org.javacs.kt.highlight.documentHighlightsAt +import org.javacs.kt.inlayhints.provideHints +import org.javacs.kt.symbols.* +import org.javacs.kt.util.* import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import java.net.URI import java.io.Closeable @@ -95,6 +89,11 @@ class KotlinTextDocumentService( codeActions(file, sp.index, params.range, params.context) } + override fun inlayHint(params: InlayHintParams): CompletableFuture> = async.compute { + val (file, _) = recover(params.textDocument.uri, params.range.start, Recompile.ALWAYS) + provideHints(file) + } + override fun hover(position: HoverParams): CompletableFuture = async.compute { reportTime { LOG.info("Hovering at {}", describePosition(position)) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt new file mode 100644 index 000000000..35c3fc613 --- /dev/null +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -0,0 +1,82 @@ +package org.javacs.kt.inlayhints + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNameIdentifierOwner +import org.eclipse.lsp4j.InlayHint +import org.eclipse.lsp4j.InlayHintKind +import org.eclipse.lsp4j.jsonrpc.messages.Either +import org.javacs.kt.CompiledFile +import org.javacs.kt.position.range +import org.javacs.kt.util.preOrderTraversal +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch +import org.jetbrains.kotlin.resolve.calls.smartcasts.getKotlinTypeForComparison +import org.jetbrains.kotlin.resolve.calls.util.getParentResolvedCall +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.error.ErrorType + +private fun PsiElement?.determineType(ctx: BindingContext): KotlinType? = + this?.let { + when (this) { + is KtDestructuringDeclarationEntry -> { + val resolvedCall = ctx[BindingContext.COMPONENT_RESOLVED_CALL, this] + resolvedCall?.resultingDescriptor?.returnType + } + is KtProperty -> { + //TODO: better handling for unresolved-type error + val type = this.getKotlinTypeForComparison(ctx) + if (type is ErrorType) null else type + } + + else -> null + } + } + +private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, arg: Name? = null): InlayHint { + val namedElement = ((this as? PsiNameIdentifierOwner)?.nameIdentifier ?: this) + val range = range(file.parse.text, namedElement.textRange) + val (pos, label) = when(kind) { + InlayHintKind.Type -> Pair(range.end, ": ${this.determineType(file.compile)}") + InlayHintKind.Parameter -> Pair(range.start, "$arg:") + } + val hint = InlayHint(pos, Either.forLeft(label)) + hint.kind = kind + hint.paddingRight = true + hint.paddingLeft = true + return hint +} + +private fun valueArgsToHints( + callExpression: KtCallExpression, + file: CompiledFile, +): List { + return callExpression.valueArguments.map { + val call = it.getParentResolvedCall(file.compile) + val arg = (call?.getArgumentMapping(it) as ArgumentMatch).valueParameter.name + it.hintBuilder(InlayHintKind.Parameter, file, arg) + } +} + + +fun provideHints(file: CompiledFile): List { + val hints = mutableListOf() + for (node in file.parse.preOrderTraversal().asIterable()) { + when (node) { + is KtCallExpression -> { + hints.addAll(valueArgsToHints(node, file)) + } + is KtDestructuringDeclaration -> { + hints.addAll(node.entries.map { it.hintBuilder(InlayHintKind.Type, file) }) + } + is KtProperty -> { + //check decleration does not include type i.e. var t1: String + if (node.typeReference == null) { + hints.add(node.hintBuilder(InlayHintKind.Type, file)) + } + } + } + } + return hints +} From da7e78625b2c820274f57e9678ddea0a4347691f Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Wed, 27 Sep 2023 20:37:39 +0100 Subject: [PATCH 02/21] include lambda hints --- .../org/javacs/kt/inlayhints/InlayHint.kt | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 35c3fc613..71977f1dd 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -6,9 +6,9 @@ import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.InlayHintKind import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.CompiledFile +import org.javacs.kt.completion.DECL_RENDERER import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch @@ -17,29 +17,31 @@ import org.jetbrains.kotlin.resolve.calls.util.getParentResolvedCall import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.error.ErrorType -private fun PsiElement?.determineType(ctx: BindingContext): KotlinType? = - this?.let { - when (this) { - is KtDestructuringDeclarationEntry -> { - val resolvedCall = ctx[BindingContext.COMPONENT_RESOLVED_CALL, this] - resolvedCall?.resultingDescriptor?.returnType - } - is KtProperty -> { - //TODO: better handling for unresolved-type error - val type = this.getKotlinTypeForComparison(ctx) - if (type is ErrorType) null else type - } - - else -> null +private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = + when (this) { + is KtDestructuringDeclarationEntry -> { + val resolvedCall = ctx[BindingContext.COMPONENT_RESOLVED_CALL, this] + resolvedCall?.resultingDescriptor?.returnType } + is KtProperty -> { + //TODO: better handling for unresolved-type error + val type = this.getKotlinTypeForComparison(ctx) + if (type is ErrorType) null else type + } + + else -> null } -private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, arg: Name? = null): InlayHint { +private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, hintLabel: String? = null): InlayHint? { val namedElement = ((this as? PsiNameIdentifierOwner)?.nameIdentifier ?: this) val range = range(file.parse.text, namedElement.textRange) val (pos, label) = when(kind) { - InlayHintKind.Type -> Pair(range.end, ": ${this.determineType(file.compile)}") - InlayHintKind.Parameter -> Pair(range.start, "$arg:") + InlayHintKind.Type -> { + this.determineType(file.compile) ?.let { + Pair(range.end, ": ${DECL_RENDERER.renderType(it)}") + } ?: return null + } + InlayHintKind.Parameter -> Pair(range.start, "$hintLabel:") } val hint = InlayHint(pos, Either.forLeft(label)) hint.kind = kind @@ -52,10 +54,10 @@ private fun valueArgsToHints( callExpression: KtCallExpression, file: CompiledFile, ): List { - return callExpression.valueArguments.map { + return callExpression.valueArguments.mapNotNull { val call = it.getParentResolvedCall(file.compile) val arg = (call?.getArgumentMapping(it) as ArgumentMatch).valueParameter.name - it.hintBuilder(InlayHintKind.Parameter, file, arg) + it.hintBuilder(InlayHintKind.Parameter, file, arg.asString()) } } @@ -68,12 +70,12 @@ fun provideHints(file: CompiledFile): List { hints.addAll(valueArgsToHints(node, file)) } is KtDestructuringDeclaration -> { - hints.addAll(node.entries.map { it.hintBuilder(InlayHintKind.Type, file) }) + hints.addAll(node.entries.mapNotNull { it.hintBuilder(InlayHintKind.Type, file) }) } is KtProperty -> { //check decleration does not include type i.e. var t1: String if (node.typeReference == null) { - hints.add(node.hintBuilder(InlayHintKind.Type, file)) + node.hintBuilder(InlayHintKind.Type, file)?.let { hints.add(it) } } } } From ede0179b9518157fd60a76e854c5c1fced142f58 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Fri, 29 Sep 2023 12:35:52 +0100 Subject: [PATCH 03/21] inferred lambda parameter type --- .../org/javacs/kt/inlayhints/InlayHint.kt | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 71977f1dd..fee88d5c8 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -9,16 +9,24 @@ import org.javacs.kt.CompiledFile import org.javacs.kt.completion.DECL_RENDERER import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.resolve.calls.smartcasts.getKotlinTypeForComparison -import org.jetbrains.kotlin.resolve.calls.util.getParentResolvedCall +import org.jetbrains.kotlin.resolve.calls.util.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.error.ErrorType private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = when (this) { + is KtParameter -> { + if (this.isLambdaParameter and (this.typeReference == null)) { + val descriptor = ctx[BindingContext.DECLARATION_TO_DESCRIPTOR, this] as CallableDescriptor + descriptor.returnType + } else null + } is KtDestructuringDeclarationEntry -> { val resolvedCall = ctx[BindingContext.COMPONENT_RESOLVED_CALL, this] resolvedCall?.resultingDescriptor?.returnType @@ -28,22 +36,19 @@ private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = val type = this.getKotlinTypeForComparison(ctx) if (type is ErrorType) null else type } - else -> null } -private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, hintLabel: String? = null): InlayHint? { +private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, label: String? = null): InlayHint? { val namedElement = ((this as? PsiNameIdentifierOwner)?.nameIdentifier ?: this) val range = range(file.parse.text, namedElement.textRange) - val (pos, label) = when(kind) { - InlayHintKind.Type -> { + val hint = when(kind) { + InlayHintKind.Type -> this.determineType(file.compile) ?.let { - Pair(range.end, ": ${DECL_RENDERER.renderType(it)}") + InlayHint(range.end, Either.forLeft(": ${DECL_RENDERER.renderType(it)}")) } ?: return null - } - InlayHintKind.Parameter -> Pair(range.start, "$hintLabel:") + InlayHintKind.Parameter -> InlayHint(range.start, Either.forLeft("$label =")) } - val hint = InlayHint(pos, Either.forLeft(label)) hint.kind = kind hint.paddingRight = true hint.paddingLeft = true @@ -57,15 +62,23 @@ private fun valueArgsToHints( return callExpression.valueArguments.mapNotNull { val call = it.getParentResolvedCall(file.compile) val arg = (call?.getArgumentMapping(it) as ArgumentMatch).valueParameter.name - it.hintBuilder(InlayHintKind.Parameter, file, arg.asString()) + it.hintBuilder(InlayHintKind.Parameter, file, label = arg.asString()) } } +private fun lambdaValueParamsToHints(node: KtLambdaArgument, file: CompiledFile): List { + return node.getLambdaExpression()!!.valueParameters.mapNotNull { + it.hintBuilder(InlayHintKind.Type, file) + } +} fun provideHints(file: CompiledFile): List { val hints = mutableListOf() for (node in file.parse.preOrderTraversal().asIterable()) { when (node) { + is KtLambdaArgument -> { + hints.addAll(lambdaValueParamsToHints(node, file)) + } is KtCallExpression -> { hints.addAll(valueArgsToHints(node, file)) } From c05c885662113fb6a9102bd69c24112869719e3a Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Fri, 29 Sep 2023 12:38:30 +0100 Subject: [PATCH 04/21] render non-lambda arguments of callable --- server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index fee88d5c8..5a331fe25 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -80,7 +80,10 @@ fun provideHints(file: CompiledFile): List { hints.addAll(lambdaValueParamsToHints(node, file)) } is KtCallExpression -> { - hints.addAll(valueArgsToHints(node, file)) + //hints are not rendered for argument of type lambda expression i.e. list.map { it } + if (node.getChildOfType() == null) { + hints.addAll(valueArgsToHints(node, file)) + } } is KtDestructuringDeclaration -> { hints.addAll(node.entries.mapNotNull { it.hintBuilder(InlayHintKind.Type, file) }) From f4d149fccc228785fb94d372ffb72302f46c33f9 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Sat, 30 Sep 2023 17:35:22 +0100 Subject: [PATCH 05/21] handle vararg parameter --- .../org/javacs/kt/inlayhints/InlayHint.kt | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 5a331fe25..822d13e5d 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -13,7 +13,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch +import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument import org.jetbrains.kotlin.resolve.calls.smartcasts.getKotlinTypeForComparison import org.jetbrains.kotlin.resolve.calls.util.* import org.jetbrains.kotlin.types.KotlinType @@ -47,7 +47,7 @@ private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, labe this.determineType(file.compile) ?.let { InlayHint(range.end, Either.forLeft(": ${DECL_RENDERER.renderType(it)}")) } ?: return null - InlayHintKind.Parameter -> InlayHint(range.start, Either.forLeft("$label =")) + InlayHintKind.Parameter -> InlayHint(range.start, Either.forLeft("$label:")) } hint.kind = kind hint.paddingRight = true @@ -55,15 +55,27 @@ private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, labe return hint } -private fun valueArgsToHints( +private fun callableArgsToHints( callExpression: KtCallExpression, file: CompiledFile, ): List { - return callExpression.valueArguments.mapNotNull { - val call = it.getParentResolvedCall(file.compile) - val arg = (call?.getArgumentMapping(it) as ArgumentMatch).valueParameter.name - it.hintBuilder(InlayHintKind.Parameter, file, label = arg.asString()) + val resolvedCall = callExpression.getResolvedCall(file.compile) + + val hints = mutableListOf() + resolvedCall?.valueArguments?.forEach { (t, u) -> + val valueArg = u.arguments.first() + + if (!valueArg.isNamed()) { + val label = (t.name).let { name -> + when (u) { + is VarargValueArgument -> "...$name" + else -> name.asString() + } + } + valueArg.asElement().hintBuilder(InlayHintKind.Parameter, file, label)?.let { hints.add(it) } + } } + return hints } private fun lambdaValueParamsToHints(node: KtLambdaArgument, file: CompiledFile): List { @@ -82,7 +94,7 @@ fun provideHints(file: CompiledFile): List { is KtCallExpression -> { //hints are not rendered for argument of type lambda expression i.e. list.map { it } if (node.getChildOfType() == null) { - hints.addAll(valueArgsToHints(node, file)) + hints.addAll(callableArgsToHints(node, file)) } } is KtDestructuringDeclaration -> { From e74909b815a54122ec718773c27ebb21fa12e7f3 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Mon, 2 Oct 2023 16:55:35 +0100 Subject: [PATCH 06/21] fix empty callable --- .../org/javacs/kt/inlayhints/InlayHint.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 822d13e5d..dff4229cd 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -63,16 +63,19 @@ private fun callableArgsToHints( val hints = mutableListOf() resolvedCall?.valueArguments?.forEach { (t, u) -> - val valueArg = u.arguments.first() + if (u.arguments.isNotEmpty()) { + val valueArg = u.arguments.first() - if (!valueArg.isNamed()) { - val label = (t.name).let { name -> - when (u) { - is VarargValueArgument -> "...$name" - else -> name.asString() + if (!valueArg.isNamed()) { + val label = (t.name).let { name -> + when (u) { + is VarargValueArgument -> "...$name" + else -> name.asString() + } } + valueArg.asElement().hintBuilder(InlayKind.ParameterHint, file, label)?.let { hints.add(it) } } - valueArg.asElement().hintBuilder(InlayHintKind.Parameter, file, label)?.let { hints.add(it) } + } } return hints From 482055347af86e7ea849de2c0f80c528ebbd9f1d Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Mon, 2 Oct 2023 16:57:42 +0100 Subject: [PATCH 07/21] support chained methods --- .../org/javacs/kt/inlayhints/InlayHint.kt | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index dff4229cd..feaa230dc 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -2,16 +2,17 @@ package org.javacs.kt.inlayhints import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.PsiWhiteSpace import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.InlayHintKind import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.CompiledFile -import org.javacs.kt.completion.DECL_RENDERER import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.lexer.KtTokens.DOT import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument import org.jetbrains.kotlin.resolve.calls.smartcasts.getKotlinTypeForComparison @@ -19,8 +20,18 @@ import org.jetbrains.kotlin.resolve.calls.util.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.error.ErrorType + +enum class InlayKind(val base: InlayHintKind) { + TypeHint(InlayHintKind.Type), + ParameterHint(InlayHintKind.Parameter), + ChainingHint(InlayHintKind.Type), +} + private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = when (this) { + is KtCallExpression -> { + this.getKotlinTypeForComparison(ctx) + } is KtParameter -> { if (this.isLambdaParameter and (this.typeReference == null)) { val descriptor = ctx[BindingContext.DECLARATION_TO_DESCRIPTOR, this] as CallableDescriptor @@ -39,17 +50,20 @@ private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = else -> null } -private fun PsiElement.hintBuilder(kind: InlayHintKind, file: CompiledFile, label: String? = null): InlayHint? { +private fun PsiElement.hintBuilder(kind: InlayKind, file: CompiledFile, label: String? = null): InlayHint? { val namedElement = ((this as? PsiNameIdentifierOwner)?.nameIdentifier ?: this) val range = range(file.parse.text, namedElement.textRange) + val hint = when(kind) { - InlayHintKind.Type -> + InlayKind.ParameterHint -> InlayHint(range.start, Either.forLeft("$label:")) + else -> this.determineType(file.compile) ?.let { - InlayHint(range.end, Either.forLeft(": ${DECL_RENDERER.renderType(it)}")) + InlayHint(range.end, Either.forLeft( + "${(kind == InlayKind.TypeHint).let { ":" }} $it" + )) } ?: return null - InlayHintKind.Parameter -> InlayHint(range.start, Either.forLeft("$label:")) } - hint.kind = kind + hint.kind = kind.base hint.paddingRight = true hint.paddingLeft = true return hint @@ -83,10 +97,16 @@ private fun callableArgsToHints( private fun lambdaValueParamsToHints(node: KtLambdaArgument, file: CompiledFile): List { return node.getLambdaExpression()!!.valueParameters.mapNotNull { - it.hintBuilder(InlayHintKind.Type, file) + it.hintBuilder(InlayKind.TypeHint, file) } } +private fun chainedMethodsHints(node: KtDotQualifiedExpression, file: CompiledFile): List { + return node.getChildrenOfType().mapNotNull { + it.hintBuilder(InlayKind.ChainingHint, file) + } +} + fun provideHints(file: CompiledFile): List { val hints = mutableListOf() for (node in file.parse.preOrderTraversal().asIterable()) { @@ -94,6 +114,14 @@ fun provideHints(file: CompiledFile): List { is KtLambdaArgument -> { hints.addAll(lambdaValueParamsToHints(node, file)) } + is KtDotQualifiedExpression -> { + ///chaining is defined as an expression whose next sibling tokens are newline and dot + (node.nextSibling as? PsiWhiteSpace)?.let { + if (it.nextSibling.node.elementType == DOT) { + hints.addAll(chainedMethodsHints(node, file)) + } + } + } is KtCallExpression -> { //hints are not rendered for argument of type lambda expression i.e. list.map { it } if (node.getChildOfType() == null) { @@ -101,12 +129,12 @@ fun provideHints(file: CompiledFile): List { } } is KtDestructuringDeclaration -> { - hints.addAll(node.entries.mapNotNull { it.hintBuilder(InlayHintKind.Type, file) }) + hints.addAll(node.entries.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) }) } is KtProperty -> { //check decleration does not include type i.e. var t1: String if (node.typeReference == null) { - node.hintBuilder(InlayHintKind.Type, file)?.let { hints.add(it) } + node.hintBuilder(InlayKind.TypeHint, file)?.let { hints.add(it) } } } } From 995bc9c27b4ea45e4dbbb3d335f2ad73c34e0868 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Mon, 2 Oct 2023 18:11:16 +0100 Subject: [PATCH 08/21] fix imports --- .../org/javacs/kt/KotlinTextDocumentService.kt | 13 ++++++++++--- .../kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt index 25153e86f..c59eacdfa 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt @@ -5,7 +5,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either import org.eclipse.lsp4j.services.LanguageClient import org.eclipse.lsp4j.services.TextDocumentService import org.javacs.kt.codeaction.codeActions -import org.javacs.kt.completion.* +import org.javacs.kt.completion.completions import org.javacs.kt.definition.goToDefinition import org.javacs.kt.diagnostic.convertDiagnostic import org.javacs.kt.formatting.formatKotlinCode @@ -19,8 +19,15 @@ import org.javacs.kt.signaturehelp.fetchSignatureHelpAt import org.javacs.kt.rename.renameSymbol import org.javacs.kt.highlight.documentHighlightsAt import org.javacs.kt.inlayhints.provideHints -import org.javacs.kt.symbols.* -import org.javacs.kt.util.* +import org.javacs.kt.symbols.documentSymbols +import org.javacs.kt.util.AsyncExecutor +import org.javacs.kt.util.Debouncer +import org.javacs.kt.util.TemporaryDirectory +import org.javacs.kt.util.describeURI +import org.javacs.kt.util.describeURIs +import org.javacs.kt.util.filePath +import org.javacs.kt.util.noResult +import org.javacs.kt.util.parseURI import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import java.net.URI import java.io.Closeable diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index feaa230dc..450df19df 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -11,12 +11,19 @@ import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.lexer.KtTokens.DOT -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDestructuringDeclaration +import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtLambdaArgument +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument import org.jetbrains.kotlin.resolve.calls.smartcasts.getKotlinTypeForComparison -import org.jetbrains.kotlin.resolve.calls.util.* +import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.error.ErrorType From 0e58752b7d59cb43bc5fd11abdb368d83b04864c Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Mon, 2 Oct 2023 18:12:00 +0100 Subject: [PATCH 09/21] fix chained hints label --- server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 450df19df..68adfe10a 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -66,7 +66,7 @@ private fun PsiElement.hintBuilder(kind: InlayKind, file: CompiledFile, label: S else -> this.determineType(file.compile) ?.let { InlayHint(range.end, Either.forLeft( - "${(kind == InlayKind.TypeHint).let { ":" }} $it" + "${(if (kind == InlayKind.TypeHint) ": " else "")}$it" )) } ?: return null } From 17bc48886bf5ca2762e3cce2cb7d803a38d85dda Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Tue, 10 Oct 2023 17:19:25 +0100 Subject: [PATCH 10/21] fix lambda declaration hint --- server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 68adfe10a..6e9bf4d56 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -7,6 +7,7 @@ import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.InlayHintKind import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.CompiledFile +import org.javacs.kt.completion.DECL_RENDERER import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal import org.jetbrains.kotlin.descriptors.CallableDescriptor @@ -66,7 +67,7 @@ private fun PsiElement.hintBuilder(kind: InlayKind, file: CompiledFile, label: S else -> this.determineType(file.compile) ?.let { InlayHint(range.end, Either.forLeft( - "${(if (kind == InlayKind.TypeHint) ": " else "")}$it" + "${(if (kind == InlayKind.TypeHint) ": " else "")}${DECL_RENDERER.renderType(it)}" )) } ?: return null } From 7c212e4b6521470a047ae396ece35e904b78b1b5 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Tue, 10 Oct 2023 17:20:01 +0100 Subject: [PATCH 11/21] tests --- .../kotlin/org/javacs/kt/InlayHintTest.kt | 154 ++++++++++++++++++ .../javacs/kt/LanguageServerTestFixture.kt | 3 + .../resources/inlayhints/ChainedMethods.kt | 25 +++ .../test/resources/inlayhints/Declarations.kt | 21 +++ .../test/resources/inlayhints/Parameters.kt | 19 +++ 5 files changed, 222 insertions(+) create mode 100644 server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt create mode 100644 server/src/test/resources/inlayhints/ChainedMethods.kt create mode 100644 server/src/test/resources/inlayhints/Declarations.kt create mode 100644 server/src/test/resources/inlayhints/Parameters.kt diff --git a/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt b/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt new file mode 100644 index 000000000..a351d7750 --- /dev/null +++ b/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt @@ -0,0 +1,154 @@ +package org.javacs.kt + +import org.eclipse.lsp4j.InlayHint +import org.eclipse.lsp4j.Position +import org.hamcrest.Matchers.isIn +import org.hamcrest.Matchers.hasSize +import org.hamcrest.Matchers.everyItem +import org.hamcrest.Matchers.containsString +import org.hamcrest.Matchers.equalTo +import org.junit.Assert.assertThat +import org.junit.Assert.assertTrue +import org.junit.Assert.assertEquals +import org.junit.Test + + +private fun predicate(pos: Position, label: String) = { + hint: InlayHint -> hint.position == pos && hint.label.left.contains(label) +} + +private fun nPredicateFilter( + hints: List, + predicates: List<(InlayHint) -> Boolean> +): List = + hints.filter { + predicates.any { p -> p(it) } + } + + +class InlayHintDeclarationTest : SingleFileTestFixture("inlayhints", "Declarations.kt") { + + private val hints = languageServer.textDocumentService.inlayHint(inlayHintParams(file, range(0, 0, 0, 0))).get() + + @Test + fun `lambda declaration hints`() { + val result = hints.filter { + it.position == Position(2, 10) + } + assertThat(result, hasSize(1)) + + val label = result.single().label.left.replaceBefore("(", "") + val regex = Regex("\\(([^)]+)\\) -> .*") + assertTrue(label.matches(regex)) + } + + @Test + fun `destrucuted declaration hints`() { + val predicates = listOf( + predicate(Position(17, 10), "Float"), + predicate(Position(17, 13), "Double"), + ) + val result = nPredicateFilter(hints, predicates) + assertThat(result, hasSize(2)) + assertThat(result, everyItem(isIn(hints))) + } + + @Test + fun `should not render hint with explicit type`() { + val result = hints.filter { + it.label.left.contains("Type") + } + assertTrue(result.isEmpty()) + } + + @Test + fun `generic type hints`() { + val expected = listOf(Position(5, 13), Position(20, 7)) + + val result = hints.filter { + it.label.left.matches(Regex(": Box<([^)]+)>")) + }.map { it.position } + + assertEquals(result.size, expected.size) + assertEquals(result.sortedBy { it.line }, expected.sortedBy { it.line }) + } + +} + +class InlayHintCallableParameterTest : SingleFileTestFixture("inlayhints", "Parameters.kt") { + + private val hints = languageServer.textDocumentService.inlayHint(inlayHintParams(file, range(0, 0, 0, 0))).get() + + @Test + fun `class parameter hints`() { + val predicates = listOf( + predicate(Position(13, 4), "x"), + predicate(Position(14, 4), "y"), + predicate(Position(15, 4), "z"), + ) + val result = nPredicateFilter(hints, predicates) + assertThat(result, hasSize(3)) + assertThat(result, everyItem(isIn(hints))) + } + + @Test + fun `has one vararg parameter hint`() { + val varargHintCount = hints.filter { + it.label.left.contains("ints") + }.size + assertThat(varargHintCount, equalTo(1)) + } + + @Test + fun `mixed parameter types`(){ + val predicates = listOf( + predicate(Position(17, 14), "d"), + predicate(Position(17, 19), "p1"), + predicate(Position(17, 25), "ints"), + ) + val result = nPredicateFilter(hints, predicates) + assertThat(result, hasSize(3)) + assertThat(result, everyItem(isIn(hints))) + } + + @Test + fun `inferred lambda parameter type`() { + val hint = hints.filter { + it.label.left.contains("Int") + } + assertThat(hint, hasSize(1)) + assertThat(hint.single().label.left, containsString("Int")) + } + +} + +class InlayHintChainedTest : SingleFileTestFixture("inlayhints", "ChainedMethods.kt") { + + private val hints = languageServer.textDocumentService.inlayHint(inlayHintParams(file, range(0, 0, 0, 0))).get() + + @Test + fun `chained hints`() { + val predicates = listOf( + predicate(Position(17, 34), "List"), + predicate(Position(18, 26), "List"), + predicate(Position(19, 19), "Array"), + ) + val result = nPredicateFilter(hints, predicates) + + assertThat(result, hasSize(3)) + assertThat(result, everyItem(isIn(hints))) + } + + @Test + fun `generic chained hints`() { + val predicates = listOf( + predicate(Position(22, 16), "A"), + predicate(Position(23, 8), "B"), + ) + val result = nPredicateFilter(hints, predicates) + + assertThat(result, hasSize(2)) + assertThat(result, everyItem(isIn(hints))) + } + +} \ No newline at end of file diff --git a/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt b/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt index 2f88a64f6..3ebffe52b 100644 --- a/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt +++ b/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt @@ -80,6 +80,9 @@ abstract class LanguageServerTestFixture(relativeWorkspaceRoot: String) : Langua fun hoverParams(relativePath: String, line: Int, column: Int): HoverParams = textDocumentPosition(relativePath, line, column).run { HoverParams(textDocument, position) } + fun inlayHintParams(relativePath: String, range: Range): InlayHintParams = + textDocumentPosition(relativePath, 0, 0).run { InlayHintParams(textDocument, range) } + fun semanticTokensParams(relativePath: String): SemanticTokensParams = textDocumentPosition(relativePath, 0, 0).run { SemanticTokensParams(textDocument) } diff --git a/server/src/test/resources/inlayhints/ChainedMethods.kt b/server/src/test/resources/inlayhints/ChainedMethods.kt new file mode 100644 index 000000000..8644e2841 --- /dev/null +++ b/server/src/test/resources/inlayhints/ChainedMethods.kt @@ -0,0 +1,25 @@ +package inlayhints + + +class A(private val self: List) { + fun a(): B { + return B(self) + } +} + +class B(private val self: List) { + fun b(): List { + return self + } +} + +val foo = listOf(1, 2, 3, 4) + +val bar = listOf("hello", "world") + .map { it.length * 2 } + .toTypedArray() + .contains(2) + +val baz = A(foo) + .a() + .b() diff --git a/server/src/test/resources/inlayhints/Declarations.kt b/server/src/test/resources/inlayhints/Declarations.kt new file mode 100644 index 000000000..8e9b8074d --- /dev/null +++ b/server/src/test/resources/inlayhints/Declarations.kt @@ -0,0 +1,21 @@ +package inlayhints + +val lambda = { n: Int, m: Double -> "$n -> $m" } + +class Box(t: T) { + var value = this +} + +data class Type( + val f: Float, + val d: Double, +) + +fun destructure() { + val type: Type + type = Type(1.0f, 2.0) + + val (x, y) = type +} + +val box = Box(0) \ No newline at end of file diff --git a/server/src/test/resources/inlayhints/Parameters.kt b/server/src/test/resources/inlayhints/Parameters.kt new file mode 100644 index 000000000..69c08cbd6 --- /dev/null +++ b/server/src/test/resources/inlayhints/Parameters.kt @@ -0,0 +1,19 @@ +package inlayhints + +data class Vec( + val x: Double, + val y: Double, + val z: Int +) + +fun print(d: Double, vararg ints: Int, cond: Boolean) {} + +val calc = { v: Vec -> v.x + v.y * v.z } + +val vec = Vec( + 2.0, + 2.2, + 1, +) +val t = print(calc(vec), 1,2,3, cond = true) +val m = listOf(0,0).map { num -> num.toDouble() } \ No newline at end of file From 97d37b8115c52559178a3b0e2baaad4a1914840f Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Wed, 11 Oct 2023 20:55:43 +0100 Subject: [PATCH 12/21] review fixes --- .../org/javacs/kt/inlayhints/InlayHint.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 6e9bf4d56..d84dc2e7b 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -12,6 +12,7 @@ import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.lexer.KtTokens.DOT +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDestructuringDeclaration import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry @@ -22,6 +23,7 @@ import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument import org.jetbrains.kotlin.resolve.calls.smartcasts.getKotlinTypeForComparison import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall @@ -51,7 +53,6 @@ private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = resolvedCall?.resultingDescriptor?.returnType } is KtProperty -> { - //TODO: better handling for unresolved-type error val type = this.getKotlinTypeForComparison(ctx) if (type is ErrorType) null else type } @@ -89,12 +90,7 @@ private fun callableArgsToHints( val valueArg = u.arguments.first() if (!valueArg.isNamed()) { - val label = (t.name).let { name -> - when (u) { - is VarargValueArgument -> "...$name" - else -> name.asString() - } - } + val label = getLabel(t.name, u) valueArg.asElement().hintBuilder(InlayKind.ParameterHint, file, label)?.let { hints.add(it) } } @@ -103,6 +99,14 @@ private fun callableArgsToHints( return hints } +private fun getLabel(name: Name, arg: ResolvedValueArgument) = + (name).let { + when (arg) { + is VarargValueArgument -> "...$it" + else -> it.asString() + } + } + private fun lambdaValueParamsToHints(node: KtLambdaArgument, file: CompiledFile): List { return node.getLambdaExpression()!!.valueParameters.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) From 88e987cc4c17132f182bfc18e3f9d5843f27d11f Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Thu, 12 Oct 2023 16:10:38 +0100 Subject: [PATCH 13/21] lambda destructure-type parameter hint --- .../main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index d84dc2e7b..0f29a676d 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -108,7 +108,16 @@ private fun getLabel(name: Name, arg: ResolvedValueArgument) = } private fun lambdaValueParamsToHints(node: KtLambdaArgument, file: CompiledFile): List { - return node.getLambdaExpression()!!.valueParameters.mapNotNull { + val params = node.getLambdaExpression()!!.valueParameters + + //hint should not be rendered when parameter is of type DestructuringDeclaration + //example: Map.forEach { (k,v) -> _ } + //lambda parameter (k,v) becomes (k :hint, v :hint) :hint <- outer hint isnt needed + params.singleOrNull()?.let { + if (it.destructuringDeclaration != null) return emptyList() + } + + return params.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) } } From 25ad6d4d9a267620fc0aada28e92112fa338865b Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Fri, 13 Oct 2023 11:19:01 +0100 Subject: [PATCH 14/21] refactor --- .../org/javacs/kt/inlayhints/InlayHint.kt | 65 ++++++++++--------- .../kotlin/org/javacs/kt/InlayHintTest.kt | 2 +- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 0f29a676d..8a04792aa 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -31,12 +31,6 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.error.ErrorType -enum class InlayKind(val base: InlayHintKind) { - TypeHint(InlayHintKind.Type), - ParameterHint(InlayHintKind.Parameter), - ChainingHint(InlayHintKind.Type), -} - private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = when (this) { is KtCallExpression -> { @@ -83,20 +77,15 @@ private fun callableArgsToHints( file: CompiledFile, ): List { val resolvedCall = callExpression.getResolvedCall(file.compile) - - val hints = mutableListOf() - resolvedCall?.valueArguments?.forEach { (t, u) -> - if (u.arguments.isNotEmpty()) { - val valueArg = u.arguments.first() - - if (!valueArg.isNamed()) { - val label = getLabel(t.name, u) - valueArg.asElement().hintBuilder(InlayKind.ParameterHint, file, label)?.let { hints.add(it) } - } - - } + val entries = resolvedCall?.valueArguments?.entries ?: return emptyList() + + return entries.mapNotNull { (t, u) -> + val valueArg = u.arguments.singleOrNull() + if (valueArg != null && !valueArg.isNamed()) { + val label = getLabel(t.name, u) + valueArg.asElement().hintBuilder(InlayKind.ParameterHint, file, label) + } else null } - return hints } private fun getLabel(name: Name, arg: ResolvedValueArgument) = @@ -128,6 +117,20 @@ private fun chainedMethodsHints(node: KtDotQualifiedExpression, file: CompiledFi } } +private fun destructuringVarHints( + node: KtDestructuringDeclaration, + file: CompiledFile +): List { + return node.entries.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) } +} + +private fun declarationHint(node: KtProperty, file: CompiledFile): InlayHint? { + //check decleration does not include type i.e. var t1: String + return if (node.typeReference == null) { + node.hintBuilder(InlayKind.TypeHint, file) + } else null +} + fun provideHints(file: CompiledFile): List { val hints = mutableListOf() for (node in file.parse.preOrderTraversal().asIterable()) { @@ -137,10 +140,11 @@ fun provideHints(file: CompiledFile): List { } is KtDotQualifiedExpression -> { ///chaining is defined as an expression whose next sibling tokens are newline and dot - (node.nextSibling as? PsiWhiteSpace)?.let { - if (it.nextSibling.node.elementType == DOT) { - hints.addAll(chainedMethodsHints(node, file)) - } + val next = (node.nextSibling as? PsiWhiteSpace) + val nextSiblingElement = next?.nextSibling?.node?.elementType + + if (nextSiblingElement != null && nextSiblingElement == DOT) { + hints.addAll(chainedMethodsHints(node, file)) } } is KtCallExpression -> { @@ -150,15 +154,16 @@ fun provideHints(file: CompiledFile): List { } } is KtDestructuringDeclaration -> { - hints.addAll(node.entries.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) }) - } - is KtProperty -> { - //check decleration does not include type i.e. var t1: String - if (node.typeReference == null) { - node.hintBuilder(InlayKind.TypeHint, file)?.let { hints.add(it) } - } + hints.addAll(destructuringVarHints(node, file)) } + is KtProperty -> declarationHint(node, file)?.let { hints.add(it) } } } return hints } + +enum class InlayKind(val base: InlayHintKind) { + TypeHint(InlayHintKind.Type), + ParameterHint(InlayHintKind.Parameter), + ChainingHint(InlayHintKind.Type), +} diff --git a/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt b/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt index a351d7750..fa2fd0ecd 100644 --- a/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt @@ -151,4 +151,4 @@ class InlayHintChainedTest : SingleFileTestFixture("inlayhints", "ChainedMethods assertThat(result, everyItem(isIn(hints))) } -} \ No newline at end of file +} From 77d109a97053f6dd9318bdae7047df77e55a2033 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Fri, 13 Oct 2023 11:46:16 +0100 Subject: [PATCH 15/21] refactor fix --- server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 8a04792aa..ebabea130 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -7,6 +7,7 @@ import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.InlayHintKind import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.CompiledFile +import org.javacs.kt.LOG import org.javacs.kt.completion.DECL_RENDERER import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal @@ -80,7 +81,7 @@ private fun callableArgsToHints( val entries = resolvedCall?.valueArguments?.entries ?: return emptyList() return entries.mapNotNull { (t, u) -> - val valueArg = u.arguments.singleOrNull() + val valueArg = u.arguments.firstOrNull() if (valueArg != null && !valueArg.isNamed()) { val label = getLabel(t.name, u) valueArg.asElement().hintBuilder(InlayKind.ParameterHint, file, label) From c58f60054f5ff5f9b3ceb14a7b833095ea7f42ae Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Mon, 16 Oct 2023 09:10:39 +0100 Subject: [PATCH 16/21] support single-expression functions --- .../org/javacs/kt/inlayhints/InlayHint.kt | 29 +++++++++++++++---- .../kotlin/org/javacs/kt/InlayHintTest.kt | 11 ++++++- .../test/resources/inlayhints/Declarations.kt | 4 ++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index ebabea130..4eecbe0df 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -7,7 +7,6 @@ import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.InlayHintKind import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.CompiledFile -import org.javacs.kt.LOG import org.javacs.kt.completion.DECL_RENDERER import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal @@ -18,7 +17,9 @@ import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDestructuringDeclaration import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtLambdaArgument +import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.psiUtil.getChildOfType @@ -34,6 +35,10 @@ import org.jetbrains.kotlin.types.error.ErrorType private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = when (this) { + is KtNamedFunction -> { + val descriptor = ctx[BindingContext.FUNCTION, this] + descriptor?.returnType + } is KtCallExpression -> { this.getKotlinTypeForComparison(ctx) } @@ -55,16 +60,19 @@ private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = } private fun PsiElement.hintBuilder(kind: InlayKind, file: CompiledFile, label: String? = null): InlayHint? { - val namedElement = ((this as? PsiNameIdentifierOwner)?.nameIdentifier ?: this) - val range = range(file.parse.text, namedElement.textRange) + val element = when(this) { + is KtFunction -> this.valueParameterList!!.originalElement + is PsiNameIdentifierOwner -> this.nameIdentifier + else -> this + } ?: return null + + val range = range(file.parse.text, element.textRange) val hint = when(kind) { InlayKind.ParameterHint -> InlayHint(range.start, Either.forLeft("$label:")) else -> this.determineType(file.compile) ?.let { - InlayHint(range.end, Either.forLeft( - "${(if (kind == InlayKind.TypeHint) ": " else "")}${DECL_RENDERER.renderType(it)}" - )) + InlayHint(range.end, Either.forLeft(DECL_RENDERER.renderType(it))) } ?: return null } hint.kind = kind.base @@ -132,10 +140,19 @@ private fun declarationHint(node: KtProperty, file: CompiledFile): InlayHint? { } else null } +private fun functionHint(node: KtNamedFunction, file: CompiledFile): InlayHint? { + //only render hints for functions without block body + //functions WITH block body will always specify return types explicitly + return if (!node.hasDeclaredReturnType() && !node.hasBlockBody()) { + node.hintBuilder(InlayKind.TypeHint, file) + } else null +} + fun provideHints(file: CompiledFile): List { val hints = mutableListOf() for (node in file.parse.preOrderTraversal().asIterable()) { when (node) { + is KtNamedFunction -> functionHint(node, file)?.let { hints.add(it) } is KtLambdaArgument -> { hints.addAll(lambdaValueParamsToHints(node, file)) } diff --git a/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt b/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt index fa2fd0ecd..3fa24f8fc 100644 --- a/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/InlayHintTest.kt @@ -66,13 +66,22 @@ class InlayHintDeclarationTest : SingleFileTestFixture("inlayhints", "Declaratio val expected = listOf(Position(5, 13), Position(20, 7)) val result = hints.filter { - it.label.left.matches(Regex(": Box<([^)]+)>")) + it.label.left.matches(Regex("Box<([^)]+)>")) }.map { it.position } assertEquals(result.size, expected.size) assertEquals(result.sortedBy { it.line }, expected.sortedBy { it.line }) } + @Test + fun `inferred hint for single-expression function`() { + val hint = hints.filter { + it.position == Position(22, 24) + } + assertThat(hint, hasSize(1)) + assertThat(hint.single().label.left, containsString("String")) + } + } class InlayHintCallableParameterTest : SingleFileTestFixture("inlayhints", "Parameters.kt") { diff --git a/server/src/test/resources/inlayhints/Declarations.kt b/server/src/test/resources/inlayhints/Declarations.kt index 8e9b8074d..051381165 100644 --- a/server/src/test/resources/inlayhints/Declarations.kt +++ b/server/src/test/resources/inlayhints/Declarations.kt @@ -18,4 +18,6 @@ fun destructure() { val (x, y) = type } -val box = Box(0) \ No newline at end of file +val box = Box(0) + +fun toStr(b: Box) = b.value.toString() From fca0ab0818c7d2b899071c31bd59f14731f2cf18 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Mon, 16 Oct 2023 10:38:07 +0100 Subject: [PATCH 17/21] refactor --- .../org/javacs/kt/inlayhints/InlayHint.kt | 114 ++++++++++-------- 1 file changed, 67 insertions(+), 47 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 4eecbe0df..5062791bc 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -81,23 +81,30 @@ private fun PsiElement.hintBuilder(kind: InlayKind, file: CompiledFile, label: S return hint } -private fun callableArgsToHints( +private fun callableArgNameHints( + acc: MutableList, callExpression: KtCallExpression, file: CompiledFile, -): List { +) { + //hints are not rendered for argument of type lambda expression i.e. list.map { it } + if (callExpression.getChildOfType() != null) { + return + } + val resolvedCall = callExpression.getResolvedCall(file.compile) - val entries = resolvedCall?.valueArguments?.entries ?: return emptyList() + val entries = resolvedCall?.valueArguments?.entries ?: return - return entries.mapNotNull { (t, u) -> + val hints = entries.mapNotNull { (t, u) -> val valueArg = u.arguments.firstOrNull() if (valueArg != null && !valueArg.isNamed()) { - val label = getLabel(t.name, u) + val label = getArgLabel(t.name, u) valueArg.asElement().hintBuilder(InlayKind.ParameterHint, file, label) } else null } + acc.addAll(hints) } -private fun getLabel(name: Name, arg: ResolvedValueArgument) = +private fun getArgLabel(name: Name, arg: ResolvedValueArgument) = (name).let { when (arg) { is VarargValueArgument -> "...$it" @@ -105,79 +112,92 @@ private fun getLabel(name: Name, arg: ResolvedValueArgument) = } } -private fun lambdaValueParamsToHints(node: KtLambdaArgument, file: CompiledFile): List { +private fun lambdaValueParamHints( + acc: MutableList, + node: KtLambdaArgument, + file: CompiledFile +) { val params = node.getLambdaExpression()!!.valueParameters //hint should not be rendered when parameter is of type DestructuringDeclaration //example: Map.forEach { (k,v) -> _ } //lambda parameter (k,v) becomes (k :hint, v :hint) :hint <- outer hint isnt needed params.singleOrNull()?.let { - if (it.destructuringDeclaration != null) return emptyList() + if (it.destructuringDeclaration != null) return } - return params.mapNotNull { + val hints = params.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) } + acc.addAll(hints) } -private fun chainedMethodsHints(node: KtDotQualifiedExpression, file: CompiledFile): List { - return node.getChildrenOfType().mapNotNull { - it.hintBuilder(InlayKind.ChainingHint, file) +private fun chainedExpressionHints( + acc: MutableList, + node: KtDotQualifiedExpression, + file: CompiledFile +) { + ///chaining is defined as an expression whose next sibling tokens are newline and dot + val next = (node.nextSibling as? PsiWhiteSpace) + val nextSiblingElement = next?.nextSibling?.node?.elementType + + if (nextSiblingElement != null && nextSiblingElement == DOT) { + val hints = node.getChildrenOfType().mapNotNull { + it.hintBuilder(InlayKind.ChainingHint, file) + } + acc.addAll(hints) } } private fun destructuringVarHints( + acc: MutableList, node: KtDestructuringDeclaration, file: CompiledFile -): List { - return node.entries.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) } +) { + val hints = node.entries.mapNotNull { + it.hintBuilder(InlayKind.TypeHint, file) + } + acc.addAll(hints) } -private fun declarationHint(node: KtProperty, file: CompiledFile): InlayHint? { +private fun declarationHint( + acc: MutableList, + node: KtProperty, + file: CompiledFile +) { //check decleration does not include type i.e. var t1: String - return if (node.typeReference == null) { - node.hintBuilder(InlayKind.TypeHint, file) - } else null + if (node.typeReference != null) return + + val hint = node.hintBuilder(InlayKind.TypeHint, file) ?: return + acc.add(hint) } -private fun functionHint(node: KtNamedFunction, file: CompiledFile): InlayHint? { +private fun functionHint( + acc: MutableList, + node: KtNamedFunction, + file: CompiledFile +) { //only render hints for functions without block body //functions WITH block body will always specify return types explicitly - return if (!node.hasDeclaredReturnType() && !node.hasBlockBody()) { - node.hintBuilder(InlayKind.TypeHint, file) - } else null + if (!node.hasDeclaredReturnType() && !node.hasBlockBody()) { + val hint = node.hintBuilder(InlayKind.TypeHint, file) ?: return + acc.add(hint) + } } fun provideHints(file: CompiledFile): List { - val hints = mutableListOf() + val res = mutableListOf() for (node in file.parse.preOrderTraversal().asIterable()) { when (node) { - is KtNamedFunction -> functionHint(node, file)?.let { hints.add(it) } - is KtLambdaArgument -> { - hints.addAll(lambdaValueParamsToHints(node, file)) - } - is KtDotQualifiedExpression -> { - ///chaining is defined as an expression whose next sibling tokens are newline and dot - val next = (node.nextSibling as? PsiWhiteSpace) - val nextSiblingElement = next?.nextSibling?.node?.elementType - - if (nextSiblingElement != null && nextSiblingElement == DOT) { - hints.addAll(chainedMethodsHints(node, file)) - } - } - is KtCallExpression -> { - //hints are not rendered for argument of type lambda expression i.e. list.map { it } - if (node.getChildOfType() == null) { - hints.addAll(callableArgsToHints(node, file)) - } - } - is KtDestructuringDeclaration -> { - hints.addAll(destructuringVarHints(node, file)) - } - is KtProperty -> declarationHint(node, file)?.let { hints.add(it) } + is KtNamedFunction -> functionHint(res, node, file) + is KtLambdaArgument -> lambdaValueParamHints(res, node, file) + is KtDotQualifiedExpression -> chainedExpressionHints(res, node, file) + is KtCallExpression -> callableArgNameHints(res, node, file) + is KtDestructuringDeclaration -> destructuringVarHints(res, node, file) + is KtProperty -> declarationHint(res, node, file) } } - return hints + return res } enum class InlayKind(val base: InlayHintKind) { From 680b3f46156370640c147d40bd39b5bf21039f31 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Tue, 17 Oct 2023 12:41:52 +0100 Subject: [PATCH 18/21] add config --- .../kotlin/org/javacs/kt/Configuration.kt | 9 ++- .../javacs/kt/KotlinTextDocumentService.kt | 2 +- .../org/javacs/kt/KotlinWorkspaceService.kt | 8 +++ .../org/javacs/kt/inlayhints/InlayHint.kt | 61 ++++++++++++------- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/Configuration.kt b/server/src/main/kotlin/org/javacs/kt/Configuration.kt index 4747b0a23..5fb9fb467 100644 --- a/server/src/main/kotlin/org/javacs/kt/Configuration.kt +++ b/server/src/main/kotlin/org/javacs/kt/Configuration.kt @@ -46,6 +46,12 @@ public data class ExternalSourcesConfiguration( var autoConvertToKotlin: Boolean = false ) +data class InlayHintsConfiguration( + var typeHints: Boolean = false, + var parameterHints: Boolean = false, + var chainedHints: Boolean = false +) + fun getStoragePath(params: InitializeParams): Path? { params.initializationOptions?.let { initializationOptions -> @@ -81,5 +87,6 @@ public data class Configuration( val completion: CompletionConfiguration = CompletionConfiguration(), val linting: LintingConfiguration = LintingConfiguration(), var indexing: IndexingConfiguration = IndexingConfiguration(), - val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration() + val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration(), + val hints: InlayHintsConfiguration = InlayHintsConfiguration() ) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt index c59eacdfa..59e07814e 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt @@ -98,7 +98,7 @@ class KotlinTextDocumentService( override fun inlayHint(params: InlayHintParams): CompletableFuture> = async.compute { val (file, _) = recover(params.textDocument.uri, params.range.start, Recompile.ALWAYS) - provideHints(file) + provideHints(file, config.hints) } override fun hover(position: HoverParams): CompletableFuture = async.compute { diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index 446a9a0dd..bbe59877e 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -102,6 +102,14 @@ class KotlinWorkspaceService( } } + // Update options for inlay hints + get("hints")?.asJsonObject?.apply { + val hints = config.hints + get("typeHints")?.asBoolean?.let { hints.typeHints = it } + get("parameterHints")?.asBoolean?.let { hints.parameterHints = it } + get("chainedHints")?.asBoolean?.let { hints.chainedHints = it } + } + // Update linter options get("linting")?.asJsonObject?.apply { val linting = config.linting diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 5062791bc..f54a3ad70 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -7,6 +7,7 @@ import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.InlayHintKind import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.CompiledFile +import org.javacs.kt.InlayHintsConfiguration import org.javacs.kt.completion.DECL_RENDERER import org.javacs.kt.position.range import org.javacs.kt.util.preOrderTraversal @@ -85,7 +86,10 @@ private fun callableArgNameHints( acc: MutableList, callExpression: KtCallExpression, file: CompiledFile, + config: InlayHintsConfiguration ) { + if (!config.parameterHints) return + //hints are not rendered for argument of type lambda expression i.e. list.map { it } if (callExpression.getChildOfType() != null) { return @@ -115,8 +119,11 @@ private fun getArgLabel(name: Name, arg: ResolvedValueArgument) = private fun lambdaValueParamHints( acc: MutableList, node: KtLambdaArgument, - file: CompiledFile + file: CompiledFile, + config: InlayHintsConfiguration ) { + if (!config.typeHints) return + val params = node.getLambdaExpression()!!.valueParameters //hint should not be rendered when parameter is of type DestructuringDeclaration @@ -135,25 +142,31 @@ private fun lambdaValueParamHints( private fun chainedExpressionHints( acc: MutableList, node: KtDotQualifiedExpression, - file: CompiledFile + file: CompiledFile, + config: InlayHintsConfiguration ) { - ///chaining is defined as an expression whose next sibling tokens are newline and dot - val next = (node.nextSibling as? PsiWhiteSpace) - val nextSiblingElement = next?.nextSibling?.node?.elementType - - if (nextSiblingElement != null && nextSiblingElement == DOT) { - val hints = node.getChildrenOfType().mapNotNull { - it.hintBuilder(InlayKind.ChainingHint, file) - } - acc.addAll(hints) + if (!config.chainedHints) return + + ///chaining is defined as an expression whose next sibling tokens are newline and dot + val next = (node.nextSibling as? PsiWhiteSpace) + val nextSiblingElement = next?.nextSibling?.node?.elementType + + if (nextSiblingElement != null && nextSiblingElement == DOT) { + val hints = node.getChildrenOfType().mapNotNull { + it.hintBuilder(InlayKind.ChainingHint, file) } + acc.addAll(hints) + } } private fun destructuringVarHints( acc: MutableList, node: KtDestructuringDeclaration, - file: CompiledFile + file: CompiledFile, + config: InlayHintsConfiguration ) { + if (!config.typeHints) return + val hints = node.entries.mapNotNull { it.hintBuilder(InlayKind.TypeHint, file) } @@ -163,8 +176,11 @@ private fun destructuringVarHints( private fun declarationHint( acc: MutableList, node: KtProperty, - file: CompiledFile + file: CompiledFile, + config: InlayHintsConfiguration ) { + if (!config.typeHints) return + //check decleration does not include type i.e. var t1: String if (node.typeReference != null) return @@ -175,8 +191,11 @@ private fun declarationHint( private fun functionHint( acc: MutableList, node: KtNamedFunction, - file: CompiledFile + file: CompiledFile, + config: InlayHintsConfiguration ) { + if (!config.typeHints) return + //only render hints for functions without block body //functions WITH block body will always specify return types explicitly if (!node.hasDeclaredReturnType() && !node.hasBlockBody()) { @@ -185,16 +204,16 @@ private fun functionHint( } } -fun provideHints(file: CompiledFile): List { +fun provideHints(file: CompiledFile, config: InlayHintsConfiguration): List { val res = mutableListOf() for (node in file.parse.preOrderTraversal().asIterable()) { when (node) { - is KtNamedFunction -> functionHint(res, node, file) - is KtLambdaArgument -> lambdaValueParamHints(res, node, file) - is KtDotQualifiedExpression -> chainedExpressionHints(res, node, file) - is KtCallExpression -> callableArgNameHints(res, node, file) - is KtDestructuringDeclaration -> destructuringVarHints(res, node, file) - is KtProperty -> declarationHint(res, node, file) + is KtNamedFunction -> functionHint(res, node, file, config) + is KtLambdaArgument -> lambdaValueParamHints(res, node, file, config) + is KtDotQualifiedExpression -> chainedExpressionHints(res, node, file, config) + is KtCallExpression -> callableArgNameHints(res, node, file, config) + is KtDestructuringDeclaration -> destructuringVarHints(res, node, file, config) + is KtProperty -> declarationHint(res, node, file, config) } } return res From 2f031129911d62e34aaeca9d9973f5968bc34f61 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Tue, 17 Oct 2023 13:26:57 +0100 Subject: [PATCH 19/21] handle destrcture declaration unused vars --- .../main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index f54a3ad70..52c490f23 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument import org.jetbrains.kotlin.resolve.calls.smartcasts.getKotlinTypeForComparison import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.util.isSingleUnderscore import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.error.ErrorType @@ -50,8 +51,14 @@ private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = } else null } is KtDestructuringDeclarationEntry -> { - val resolvedCall = ctx[BindingContext.COMPONENT_RESOLVED_CALL, this] - resolvedCall?.resultingDescriptor?.returnType + //skip unused variable denoted by underscore + //https://kotlinlang.org/docs/destructuring-declarations.html#underscore-for-unused-variables + if (this.isSingleUnderscore) { + null + } else { + val resolvedCall = ctx[BindingContext.COMPONENT_RESOLVED_CALL, this] + resolvedCall?.resultingDescriptor?.returnType + } } is KtProperty -> { val type = this.getKotlinTypeForComparison(ctx) From e6a52e0d15a1f7f1a83422546edb24b50f4d16bc Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Tue, 17 Oct 2023 17:37:12 +0100 Subject: [PATCH 20/21] fixes --- .../src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt | 2 +- .../test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index bbe59877e..5aa1e7381 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -103,7 +103,7 @@ class KotlinWorkspaceService( } // Update options for inlay hints - get("hints")?.asJsonObject?.apply { + get("inlayHints")?.asJsonObject?.apply { val hints = config.hints get("typeHints")?.asBoolean?.let { hints.typeHints = it } get("parameterHints")?.asBoolean?.let { hints.parameterHints = it } diff --git a/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt b/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt index 3ebffe52b..ae65bbe97 100644 --- a/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt +++ b/server/src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt @@ -36,6 +36,12 @@ abstract class LanguageServerTestFixture(relativeWorkspaceRoot: String) : Langua name = workspaceRoot.fileName.toString() uri = workspaceRoot.toUri().toString() }) + + languageServer.config.hints.apply { + this.typeHints = true + this.parameterHints = true + this.chainedHints = true + } languageServer.sourcePath.indexEnabled = false languageServer.connect(this) languageServer.initialize(init).join() From 0f12d0a4c6038029e6dbbe076c84574ad36ebb39 Mon Sep 17 00:00:00 2001 From: Elam Cohavi Date: Tue, 17 Oct 2023 18:41:30 +0100 Subject: [PATCH 21/21] suppress detekt rule --- server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt index 52c490f23..4644a8987 100644 --- a/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt +++ b/server/src/main/kotlin/org/javacs/kt/inlayhints/InlayHint.kt @@ -67,6 +67,7 @@ private fun PsiElement.determineType(ctx: BindingContext): KotlinType? = else -> null } +@Suppress("ReturnCount") private fun PsiElement.hintBuilder(kind: InlayKind, file: CompiledFile, label: String? = null): InlayHint? { val element = when(this) { is KtFunction -> this.valueParameterList!!.originalElement @@ -89,6 +90,7 @@ private fun PsiElement.hintBuilder(kind: InlayKind, file: CompiledFile, label: S return hint } +@Suppress("ReturnCount") private fun callableArgNameHints( acc: MutableList, callExpression: KtCallExpression, @@ -180,6 +182,7 @@ private fun destructuringVarHints( acc.addAll(hints) } +@Suppress("ReturnCount") private fun declarationHint( acc: MutableList, node: KtProperty,