Resolve array access RHS always as the last argument of the call

Also do not attempt to match any of the arguments in the brackets with the last
parameter of the 'set' method

 #KT-10633 Fixed
This commit is contained in:
Alexander Udalov
2016-01-14 02:36:57 +03:00
parent ccef1ad49e
commit 8fe964f269
9 changed files with 199 additions and 32 deletions
@@ -19,10 +19,13 @@ package org.jetbrains.kotlin.resolve.calls;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor;
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
@@ -32,6 +35,7 @@ import org.jetbrains.kotlin.resolve.calls.model.*;
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -118,15 +122,18 @@ public class ValueArgumentsToParametersMapper {
// We saw only positioned arguments so far
private final ProcessorState positionedOnly = new ProcessorState() {
private int currentParameter = 0;
private int numberOfParametersForPositionedArguments() {
int size = candidateCall.getCandidateDescriptor().getValueParameters().size();
return call.getCallType() == Call.CallType.ARRAY_SET_METHOD ? size - 1 : size;
}
@Nullable
public ValueParameterDescriptor nextValueParameter() {
List<ValueParameterDescriptor> parameters = candidateCall.getCandidateDescriptor().getValueParameters();
if (currentParameter >= parameters.size()) return null;
if (currentParameter >= numberOfParametersForPositionedArguments()) return null;
ValueParameterDescriptor head = parameters.get(currentParameter);
ValueParameterDescriptor head = candidateCall.getCandidateDescriptor().getValueParameters().get(currentParameter);
// If we found a vararg parameter, we are stuck with it forever
if (head.getVarargElementType() == null) {
@@ -142,20 +149,27 @@ public class ValueArgumentsToParametersMapper {
}
@Override
public ProcessorState processPositionedArgument(@NotNull ValueArgument argument, int index) {
ValueParameterDescriptor valueParameterDescriptor = nextValueParameter();
public ProcessorState processPositionedArgument(@NotNull ValueArgument argument) {
processArgument(argument, nextValueParameter());
return positionedOnly;
}
if (valueParameterDescriptor != null) {
usedParameters.add(valueParameterDescriptor);
putVararg(valueParameterDescriptor, argument);
@Override
public ProcessorState processArraySetRHS(@NotNull ValueArgument argument) {
processArgument(argument, CollectionsKt.lastOrNull(candidateCall.getCandidateDescriptor().getValueParameters()));
return positionedOnly;
}
private void processArgument(@NotNull ValueArgument argument, @Nullable ValueParameterDescriptor parameter) {
if (parameter != null) {
usedParameters.add(parameter);
putVararg(parameter, argument);
}
else {
report(TOO_MANY_ARGUMENTS.on(argument.asElement(), candidateCall.getCandidateDescriptor()));
unmappedArguments.add(argument);
setStatus(WEAK_ERROR);
}
return positionedOnly;
}
};
@@ -220,27 +234,34 @@ public class ValueArgumentsToParametersMapper {
}
@Override
public ProcessorState processPositionedArgument(
@NotNull ValueArgument argument, int index
) {
public ProcessorState processPositionedArgument(@NotNull ValueArgument argument) {
report(MIXING_NAMED_AND_POSITIONED_ARGUMENTS.on(argument.asElement()));
setStatus(WEAK_ERROR);
unmappedArguments.add(argument);
return positionedThenNamed;
}
@Override
public ProcessorState processArraySetRHS(@NotNull ValueArgument argument) {
throw new IllegalStateException("Array set RHS cannot appear after a named argument syntactically: " + argument);
}
};
public void process() {
ProcessorState state = positionedOnly;
boolean isArraySetMethod = call.getCallType() == Call.CallType.ARRAY_SET_METHOD;
List<? extends ValueArgument> argumentsInParentheses = CallUtilKt.getValueArgumentsInParentheses(call);
for (int i = 0; i < argumentsInParentheses.size(); i++) {
ValueArgument valueArgument = argumentsInParentheses.get(i);
for (Iterator<? extends ValueArgument> iterator = argumentsInParentheses.iterator(); iterator.hasNext(); ) {
ValueArgument valueArgument = iterator.next();
if (valueArgument.isNamed()) {
state = state.processNamedArgument(valueArgument);
}
else if (isArraySetMethod && !iterator.hasNext()) {
state = state.processArraySetRHS(valueArgument);
}
else {
state = state.processPositionedArgument(valueArgument, i);
state = state.processPositionedArgument(valueArgument);
}
}
@@ -341,9 +362,11 @@ public class ValueArgumentsToParametersMapper {
private interface ProcessorState {
ProcessorState processNamedArgument(@NotNull ValueArgument argument);
ProcessorState processPositionedArgument(@NotNull ValueArgument argument, int index);
}
ProcessorState processPositionedArgument(@NotNull ValueArgument argument);
ProcessorState processArraySetRHS(@NotNull ValueArgument argument);
}
}
private ValueArgumentsToParametersMapper() {}
@@ -0,0 +1,35 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
object A {
operator fun set(x: Int, y: String = "y", z: Double) {
}
}
object B {
operator fun set(x: Int, y: String = "y", z: Double = 3.14, w: Char = 'w', v: Boolean) {
}
}
object D {
operator fun set(x: Int, vararg y: String, z: Double) {
}
}
object Z {
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun set() {
}
}
fun test() {
A[0] = <!TYPE_MISMATCH!>""<!>
A[0] = 2.72
B[0] = <!TYPE_MISMATCH!>""<!>
B[0] = <!CONSTANT_EXPECTED_TYPE_MISMATCH!>2.72<!>
B[0] = true
D[0] = <!TYPE_MISMATCH!>""<!>
D[0] = 2.72
Z[<!TOO_MANY_ARGUMENTS!>0<!>] = <!TOO_MANY_ARGUMENTS!>""<!>
}
@@ -0,0 +1,35 @@
package
public fun test(): kotlin.Unit
public object A {
private constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final operator fun set(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.String = ..., /*2*/ z: kotlin.Double): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object B {
private constructor B()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final operator fun set(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.String = ..., /*2*/ z: kotlin.Double = ..., /*3*/ w: kotlin.Char = ..., /*4*/ v: kotlin.Boolean): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object D {
private constructor D()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final operator fun set(/*0*/ x: kotlin.Int, /*1*/ vararg y: kotlin.String /*kotlin.Array<out kotlin.String>*/, /*2*/ z: kotlin.Double): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object Z {
private constructor Z()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final operator fun set(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,10 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
class A {
operator fun get(x: Int) {}
operator fun set(x: String, value: Int) {}
fun d(x: Int) {
this["", <!TOO_MANY_ARGUMENTS!>1<!>] = 1
}
}
@@ -0,0 +1,11 @@
package
public final class A {
public constructor A()
public final fun d(/*0*/ x: kotlin.Int): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final operator fun get(/*0*/ x: kotlin.Int): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final operator fun set(/*0*/ x: kotlin.String, /*1*/ value: kotlin.Int): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,19 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
var count = 0
operator fun Int.get(s: Int): Int {
count++
return this + s
}
operator fun Int.set(s: Int, x: String = "", z: Int) {
}
fun main(args: Array<String>) {
1[2] = 1
1.set(2, z = 1)
1[2] += 1
1.set(2, <!CONSTANT_EXPECTED_TYPE_MISMATCH!>1<!><!NO_VALUE_FOR_PARAMETER!>)<!>
}
@@ -0,0 +1,6 @@
package
public var count: kotlin.Int
public fun main(/*0*/ args: kotlin.Array<kotlin.String>): kotlin.Unit
public operator fun kotlin.Int.get(/*0*/ s: kotlin.Int): kotlin.Int
public operator fun kotlin.Int.set(/*0*/ s: kotlin.Int, /*1*/ x: kotlin.String = ..., /*2*/ z: kotlin.Int): kotlin.Unit
@@ -2604,6 +2604,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/checkArguments"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("arrayAccessSet.kt")
public void testArrayAccessSet() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/checkArguments/arrayAccessSet.kt");
doTest(fileName);
}
@TestMetadata("arrayAccessSetTooManyArgs.kt")
public void testArrayAccessSetTooManyArgs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/checkArguments/arrayAccessSetTooManyArgs.kt");
doTest(fileName);
}
@TestMetadata("booleanExpressions.kt")
public void testBooleanExpressions() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/checkArguments/booleanExpressions.kt");
@@ -13077,6 +13089,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("kt10633.kt")
public void testKt10633() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/regressions/kt10633.kt");
doTest(fileName);
}
@TestMetadata("kt127.kt")
public void testKt127() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/regressions/kt127.kt");
@@ -313,14 +313,19 @@ abstract class KotlinParameterInfoWithCallHandlerBase<TArgumentList : KtElement,
return SignatureInfo(overload, { null }, null, isGrey = false)
}
assert(call.valueArguments.size >= currentArgumentIndex)
val isArraySetMethod = call.callType == Call.CallType.ARRAY_SET_METHOD
val argumentsBeforeCurrent = call.valueArguments.subList(0, currentArgumentIndex)
val arguments = call.valueArguments.let { args ->
// For array set method call, we're only interested in the arguments in brackets which are all except the last one
if (isArraySetMethod) args.dropLast(1) else args
}
assert(arguments.size >= currentArgumentIndex)
val callToUse: Call
val currentArgument: ValueArgument
if (call.valueArguments.size > currentArgumentIndex) {
currentArgument = call.valueArguments[currentArgumentIndex]
if (arguments.size > currentArgumentIndex) {
currentArgument = arguments[currentArgumentIndex]
callToUse = call
}
else {
@@ -334,9 +339,12 @@ abstract class KotlinParameterInfoWithCallHandlerBase<TArgumentList : KtElement,
override fun isExternal() = false
}
callToUse = object : DelegatingCall(call) {
val arguments = call.valueArguments + currentArgument
val argumentsWithCurrent =
arguments + currentArgument +
// For array set method call, also add the argument in the right-hand side
(if (isArraySetMethod) listOf(call.valueArguments.last()) else listOf())
override fun getValueArguments() = arguments
override fun getValueArguments() = argumentsWithCurrent
override fun getFunctionLiteralArguments() = emptyList<LambdaArgument>()
override fun getValueArgumentList() = null
}
@@ -347,20 +355,22 @@ abstract class KotlinParameterInfoWithCallHandlerBase<TArgumentList : KtElement,
val resultingDescriptor = resolvedCall.resultingDescriptor
fun argumentToParameter(argument: ValueArgument): ValueParameterDescriptor? {
val parameter = (resolvedCall.getArgumentMapping(argument) as? ArgumentMatch)?.valueParameter ?: return null
if (call.callType == Call.CallType.ARRAY_SET_METHOD && parameter.index == resultingDescriptor.valueParameters.lastIndex) return null
return parameter
return (resolvedCall.getArgumentMapping(argument) as? ArgumentMatch)?.valueParameter
}
val highlightParameterIndex = argumentToParameter(currentArgument)?.index
if (!(argumentsBeforeCurrent + currentArgument).all { argumentToParameter(it) != null }) { // some of arguments before the current one (or the current one) are not mapped to any of the parameters
val argumentsBeforeCurrent = arguments.subList(0, currentArgumentIndex)
if ((argumentsBeforeCurrent + currentArgument).any { argumentToParameter(it) == null }) {
// some of arguments before the current one (or the current one) are not mapped to any of the parameters
return SignatureInfo(resultingDescriptor, ::argumentToParameter, highlightParameterIndex, isGrey = true)
}
// grey out if not all arguments before the current are matched
val isGrey = argumentsBeforeCurrent
.any { argument -> resolvedCall.getArgumentMapping(argument).isError() && !argument.hasError(bindingContext) /* ignore arguments that has error type */ }
val isGrey = argumentsBeforeCurrent.any { argument ->
resolvedCall.getArgumentMapping(argument).isError() &&
!argument.hasError(bindingContext) /* ignore arguments that have error type */
}
return SignatureInfo(resultingDescriptor, ::argumentToParameter, highlightParameterIndex, isGrey)
}