KotlinHighLevelFunctionParameterInfoHandler: Disable candidates when there are arguments before the current that don't match the parameter type.

GitOrigin-RevId: 412eb2043c3c4541b9e5785980cde2fd5ed90476
This commit is contained in:
Mark Punzalan
2022-02-08 08:47:57 +00:00
committed by intellij-monorepo-bot
parent 77c2ee6c5b
commit 0ce5fa8955
13 changed files with 154 additions and 18 deletions
@@ -49,7 +49,7 @@ class KotlinHighLevelLambdaParameterInfoHandler :
override fun getArgumentListAllowedParentClasses() = setOf(KtLambdaArgument::class.java)
override fun getParameterIndex(context: UpdateParameterInfoContext, argumentList: KtLambdaArgument): Int {
override fun getCurrentArgumentIndex(context: UpdateParameterInfoContext, argumentList: KtLambdaArgument): Int {
val size = (argumentList.parent as? KtCallElement)?.valueArguments?.size ?: 1
return size - 1
}
@@ -131,8 +131,8 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
if (context.parameterOwner !== argumentList) {
context.removeHint()
}
val parameterIndex = getParameterIndex(context, argumentList)
context.setCurrentParameter(parameterIndex)
val currentArgumentIndex = getCurrentArgumentIndex(context, argumentList)
context.setCurrentParameter(currentArgumentIndex)
val callElement = argumentList.parent as? KtElement ?: return
analyse(callElement) {
@@ -191,6 +191,20 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
}
}
var hasTypeMismatchBeforeCurrent = false
for ((index, argument) in arguments.withIndex()) {
if (index >= currentArgumentIndex) break
val parameterForArgument = argumentMapping[argument] ?: continue
if (parameterForArgument == setValueParameter) continue
val argumentType = argument.getKtType() ?: error("Argument should have a KtType")
val parameterType = parameterForArgument.annotatedType.type
if (argumentType.isNotSubTypeOf(parameterType)) {
hasTypeMismatchBeforeCurrent = true
break
}
}
// TODO: This should be changed when there are multiple candidates available; need to know which one the call is resolved to
val isCallResolvedToCandidate = candidates.size == 1
@@ -201,13 +215,14 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
argumentToParameterIndex,
valueParameters.size,
parameterIndexToText,
isCallResolvedToCandidate
isCallResolvedToCandidate,
hasTypeMismatchBeforeCurrent
)
}
}
}
protected open fun getParameterIndex(context: UpdateParameterInfoContext, argumentList: TArgumentList): Int {
protected open fun getCurrentArgumentIndex(context: UpdateParameterInfoContext, argumentList: TArgumentList): Int {
val offset = context.offset
return argumentList.allChildren
.takeWhile { it.startOffset < offset }
@@ -280,7 +295,8 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
*
* `setupUIComponentPresentation()` is called with `disabled = true` when any of the following are true:
* 1. If the argument on the cursor does NOT map to a parameter (e.g., on N-th argument but there are < N parameters),
* 2. If any of the arguments before the cursor do NOT match the type of the corresponding parameter.
* 2. If any of the arguments before the cursor do NOT match the type of the corresponding parameter (ignoring arguments with type
* errors, e.g., unresolved) or do NOT map to a parameter (e.g., named argument with an unknown name).
* 3. If the cursor is after a trailing comma with no argument, AND LanguageFeature.TrailingCommas is DISABLED, AND there are
* already enough arguments in the call. (We assume the user is about to enter an argument in that position.)
*
@@ -298,7 +314,8 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
if (currentArgumentIndex < 0) return false
val callInfo = itemToShow.callInfo ?: return false
val (callElement, valueArguments, arguments, argumentMapping, valueParameterCount, parameterIndexToText, isCallResolvedToCandidate) = callInfo
val (callElement, valueArguments, arguments, argumentToParameterIndex, valueParameterCount, parameterIndexToText,
isCallResolvedToCandidate, hasTypeMismatchBeforeCurrent) = callInfo
val supportsMixedNamedArgumentsInTheirOwnPosition =
callElement.languageVersionSettings.supportsFeature(LanguageFeature.MixedNamedArgumentsInTheirOwnPosition)
@@ -316,6 +333,7 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
var highlightStartOffset = -1
var highlightEndOffset = -1
var isDisabledBeforeHighlight = false
var hasUnmappedArgumentBeforeCurrent = false
val usedParameterIndices = HashSet<Int>()
val text = buildString {
var namedMode = false
@@ -363,7 +381,14 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
if (valueArguments != null) {
for (valueArgument in valueArguments) {
val parameterIndex = argumentMapping[valueArgument.getArgumentExpression()] ?: continue
val parameterIndex = argumentToParameterIndex[valueArgument.getArgumentExpression()]
if (parameterIndex == null) {
if (argumentIndex < currentArgumentIndex) {
hasUnmappedArgumentBeforeCurrent = true
}
argumentIndex++
continue
}
if (!usedParameterIndices.add(parameterIndex)) continue
if (valueArgument.isNamed() &&
@@ -380,7 +405,14 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
} else {
// This is for array get/set calls which don't have KtValueArguments.
for (argument in arguments) {
val parameterIndex = argumentMapping[argument] ?: continue
val parameterIndex = argumentToParameterIndex[argument]
if (parameterIndex == null) {
if (argumentIndex <= currentArgumentIndex) {
hasUnmappedArgumentBeforeCurrent = true
}
argumentIndex++
continue
}
if (!usedParameterIndices.add(parameterIndex)) continue
val shouldHighlight = argumentIndex == currentArgumentIndex
@@ -417,11 +449,7 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
val afterTrailingComma = arguments.isNotEmpty() && currentArgumentIndex == arguments.size
val tooManyArgs = allParametersUsed && ((!supportsTrailingCommas && afterTrailingComma) || arguments.size > valueParameterCount)
// TODO: Also disable if not all arguments before the current are matched. Need to check this in updateParameterInfo()
// See UpdateInTyping.kt tests
val hasMismatchBeforeCurrent = false
val isDisabled = tooManyArgs || hasMismatchBeforeCurrent
val isDisabled = tooManyArgs || hasTypeMismatchBeforeCurrent || hasUnmappedArgumentBeforeCurrent
context.setupUIComponentPresentation(
text,
@@ -443,7 +471,8 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
val argumentToParameterIndex: LinkedHashMap<KtExpression, Int>,
val valueParameterCount: Int,
val parameterIndexToText: Map<Int, String>,
val isCallResolvedToCandidate: Boolean
val isCallResolvedToCandidate: Boolean,
val hasTypeMismatchBeforeCurrent: Boolean,
)
data class CandidateInfo(
@@ -402,6 +402,36 @@ public abstract class FirParameterInfoTestGenerated extends AbstractFirParameter
runTest("../idea/tests/testData/parameterInfo/functionCall/TypeInference.kt");
}
@TestMetadata("UnmappedAfterCurrent.kt")
public void testUnmappedAfterCurrent() throws Exception {
runTest("../idea/tests/testData/parameterInfo/functionCall/UnmappedAfterCurrent.kt");
}
@TestMetadata("UnmappedBeforeCurrent.kt")
public void testUnmappedBeforeCurrent() throws Exception {
runTest("../idea/tests/testData/parameterInfo/functionCall/UnmappedBeforeCurrent.kt");
}
@TestMetadata("UnmappedCurrent.kt")
public void testUnmappedCurrent() throws Exception {
runTest("../idea/tests/testData/parameterInfo/functionCall/UnmappedCurrent.kt");
}
@TestMetadata("UnresolvedAfterCurrent.kt")
public void testUnresolvedAfterCurrent() throws Exception {
runTest("../idea/tests/testData/parameterInfo/functionCall/UnresolvedAfterCurrent.kt");
}
@TestMetadata("UnresolvedBeforeCurrent.kt")
public void testUnresolvedBeforeCurrent() throws Exception {
runTest("../idea/tests/testData/parameterInfo/functionCall/UnresolvedBeforeCurrent.kt");
}
@TestMetadata("UnresolvedCurrent.kt")
public void testUnresolvedCurrent() throws Exception {
runTest("../idea/tests/testData/parameterInfo/functionCall/UnresolvedCurrent.kt");
}
@TestMetadata("UpdateOnTyping.kt")
public void testUpdateOnTyping() throws Exception {
runTest("../idea/tests/testData/parameterInfo/functionCall/UpdateOnTyping.kt");
@@ -402,6 +402,36 @@ public abstract class ParameterInfoTestGenerated extends AbstractParameterInfoTe
runTest("testData/parameterInfo/functionCall/TypeInference.kt");
}
@TestMetadata("UnmappedAfterCurrent.kt")
public void testUnmappedAfterCurrent() throws Exception {
runTest("testData/parameterInfo/functionCall/UnmappedAfterCurrent.kt");
}
@TestMetadata("UnmappedBeforeCurrent.kt")
public void testUnmappedBeforeCurrent() throws Exception {
runTest("testData/parameterInfo/functionCall/UnmappedBeforeCurrent.kt");
}
@TestMetadata("UnmappedCurrent.kt")
public void testUnmappedCurrent() throws Exception {
runTest("testData/parameterInfo/functionCall/UnmappedCurrent.kt");
}
@TestMetadata("UnresolvedAfterCurrent.kt")
public void testUnresolvedAfterCurrent() throws Exception {
runTest("testData/parameterInfo/functionCall/UnresolvedAfterCurrent.kt");
}
@TestMetadata("UnresolvedBeforeCurrent.kt")
public void testUnresolvedBeforeCurrent() throws Exception {
runTest("testData/parameterInfo/functionCall/UnresolvedBeforeCurrent.kt");
}
@TestMetadata("UnresolvedCurrent.kt")
public void testUnresolvedCurrent() throws Exception {
runTest("testData/parameterInfo/functionCall/UnresolvedCurrent.kt");
}
@TestMetadata("UpdateOnTyping.kt")
public void testUpdateOnTyping() throws Exception {
runTest("testData/parameterInfo/functionCall/UpdateOnTyping.kt");
@@ -1,4 +1,3 @@
// IGNORE_FIR
class A {
operator fun get(x: Int) {}
operator fun set(x: String, y: Int, value: Int) {}
@@ -1,4 +1,3 @@
// IGNORE_FIR
fun foo() {
fun fff(p: String, c: Char) {}
@@ -1,3 +1,5 @@
// IGNORE_FIR
// TODO: Fails because no argument mapping is available when there are multiple ambiguous candidates
open class A(x: Int) {
fun m(x: Int, y: Boolean) = 2
fun m(x: Int) = 1
@@ -0,0 +1,8 @@
fun m(x: Int, y: Boolean) = 2
fun d() {
m(<caret>y = false, unmapped = false)
}
/*
Text: (<highlight>[y: Boolean]</highlight>, [x: Int]), Disabled: false, Strikeout: false, Green: true
*/
@@ -0,0 +1,8 @@
fun m(x: Int, y: Boolean) = 2
fun d() {
m(unmapped = 1, <caret>y = false)
}
/*
Text: (<highlight>[y: Boolean]</highlight>, [x: Int]), Disabled: true, Strikeout: false, Green: true
*/
@@ -0,0 +1,8 @@
fun m(x: Int, y: Boolean) = 2
fun d() {
m(y = false, <caret>unmapped = false)
}
/*
Text: (<disabled>[y: Boolean],</disabled><highlight> </highlight>[x: Int]), Disabled: false, Strikeout: false, Green: true
*/
@@ -0,0 +1,8 @@
fun m(x: Int, y: Boolean) = 2
fun d() {
m(1, <caret>unresolved)
}
/*
Text: (x: Int, <highlight>y: Boolean</highlight>), Disabled: false, Strikeout: false, Green: true
*/
@@ -0,0 +1,8 @@
fun m(x: Int, y: Boolean) = 2
fun d() {
m(unresolved, <caret>true)
}
/*
Text: (x: Int, <highlight>y: Boolean</highlight>), Disabled: false, Strikeout: false, Green: true
*/
@@ -0,0 +1,8 @@
fun m(x: Int, y: Boolean) = 2
fun d() {
m(<caret>1, unresolved)
}
/*
Text: (<highlight>x: Int</highlight>, y: Boolean), Disabled: false, Strikeout: false, Green: true
*/
@@ -1,4 +1,3 @@
// IGNORE_FIR
fun fff(p: String, c: Char) {}
fun foo() {