From 45a0873afa3c3ee64d2387a979b0af00008007b6 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Fri, 20 Apr 2012 19:08:29 +0400 Subject: [PATCH] KT-1835 cannot call a Java API which has a method from(String) and from(String...) The problem was in the "more specific" relation, that didn't pay enough attention to varargs. The correct behavior is in the spirit of JLS 15.12.2 (as of Java 5): * a fixed-arity function always wins over a variable-arity functions * if two vararg functions are compared, their parameters are checked for subtyping. In the latter case, the candidates may have different number of formal parameters, so we compare the matching parts and then check the rest against the vararg parameter. #KT-1835 Fixed --- .../calls/OverloadingConflictResolver.java | 91 ++++++++++++++----- .../MoreSpecificVarargsOfEqualLength.jet | 9 ++ .../varargs/MostSepcificVarargsWithJava.jet | 19 ++++ .../diagnostics/tests/varargs/kt1835.jet | 12 +++ .../MoreSpecificVarargsOfEqualLength.jet | 9 ++ 5 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/varargs/MoreSpecificVarargsOfEqualLength.jet create mode 100644 compiler/testData/diagnostics/tests/varargs/MostSepcificVarargsWithJava.jet create mode 100644 compiler/testData/diagnostics/tests/varargs/kt1835.jet create mode 100644 compiler/testData/resolve/varargs/MoreSpecificVarargsOfEqualLength.jet diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/OverloadingConflictResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/OverloadingConflictResolver.java index 44c37c97435..1edce3205fa 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/OverloadingConflictResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/OverloadingConflictResolver.java @@ -25,10 +25,10 @@ import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.OverridingUtil; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; -import org.jetbrains.jet.lang.types.checker.JetTypeChecker; -import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.TypeUtils; +import org.jetbrains.jet.lang.types.checker.JetTypeChecker; +import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; import java.util.List; import java.util.Set; @@ -95,29 +95,73 @@ public class OverloadingConflictResolver { int fSize = fParams.size(); int gSize = gParams.size(); - if (fSize > gSize) return false; - for (int i = 0; i < gSize; i++) { - ValueParameterDescriptor gParam = gParams.get(i); - JetType gParamType = gParam.getType(); - if (i >= fSize) { - // f() has fewer parameters than g() - // The point is: g() may have a last vararg that doesn't get any arguments - // In this case f() is more specific, because one can explicitly pass - // an empty array to g() and make it preferred - if (i != gSize - 1 || gParam.getVarargElementType() == null) { + boolean fIsVararg = isVariableArity(fParams); + boolean gIsVararg = isVariableArity(gParams); + + if (!fIsVararg && gIsVararg) return true; + if (fIsVararg && !gIsVararg) return false; + + if (!fIsVararg && !gIsVararg) { + if (fSize != gSize) return false; + + for (int i = 0; i < fSize; i++) { + ValueParameterDescriptor fParam = fParams.get(i); + ValueParameterDescriptor gParam = gParams.get(i); + + JetType fParamType = fParam.getType(); + JetType gParamType = gParam.getType(); + + if (!typeMoreSpecific(fParamType, gParamType)) { return false; } - return true; - } - - JetType fParamType = fParams.get(i).getType(); - - if (!typeMoreSpecific(fParamType, gParamType)) { - return false; } } - + + if (fIsVararg && gIsVararg) { + // Check matching parameters + int minSize = Math.min(fSize, gSize); + for (int i = 0; i < minSize - 1; i++) { + ValueParameterDescriptor fParam = fParams.get(i); + ValueParameterDescriptor gParam = gParams.get(i); + + JetType fParamType = fParam.getType(); + JetType gParamType = gParam.getType(); + + if (!typeMoreSpecific(fParamType, gParamType)) { + return false; + } + } + + // Check the non-matching parameters of one function against the vararg parameter of the other funciton + // Example: + // f(a : A, vararg vf : T) + // g(vararg vg : T) + // here we check that typeof(a) < typeof(vg) and elementTypeOf(vf) < elementTypeOf(vg) + if (fSize < gSize) { + ValueParameterDescriptor fParam = fParams.get(fSize - 1); + JetType fParamType = fParam.getVarargElementType(); + assert fParamType != null : "fIsVararg guarantees this"; + for (int i = fSize - 1; i < gSize; i++) { + ValueParameterDescriptor gParam = gParams.get(i); + if (!typeMoreSpecific(fParamType, gParam.getType())) { + return false; + } + } + } + else { + ValueParameterDescriptor gParam = gParams.get(gSize - 1); + JetType gParamType = gParam.getVarargElementType(); + assert gParamType != null : "gIsVararg guarantees this"; + for (int i = gSize - 1; i < fSize; i++) { + ValueParameterDescriptor fParam = fParams.get(i); + if (!typeMoreSpecific(fParam.getType(), gParamType)) { + return false; + } + } + } + } + if (discriminateGenericDescriptors && isGeneric(f)) { if (!isGeneric(g)) { return false; @@ -130,7 +174,12 @@ public class OverloadingConflictResolver { return true; } - + + private boolean isVariableArity(List fParams) { + int fSize = fParams.size(); + return fSize > 0 && fParams.get(fSize - 1).getVarargElementType() != null; + } + private boolean isGeneric(CallableDescriptor f) { return !f.getOriginal().getTypeParameters().isEmpty(); } diff --git a/compiler/testData/diagnostics/tests/varargs/MoreSpecificVarargsOfEqualLength.jet b/compiler/testData/diagnostics/tests/varargs/MoreSpecificVarargsOfEqualLength.jet new file mode 100644 index 00000000000..ef8688c4899 --- /dev/null +++ b/compiler/testData/diagnostics/tests/varargs/MoreSpecificVarargsOfEqualLength.jet @@ -0,0 +1,9 @@ +fun main(d : D) { + d.from("") + d.from(1) +} + +class D { + fun from(vararg a : Any){} + fun from(vararg a : String){} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/varargs/MostSepcificVarargsWithJava.jet b/compiler/testData/diagnostics/tests/varargs/MostSepcificVarargsWithJava.jet new file mode 100644 index 00000000000..dcc1d93ae4b --- /dev/null +++ b/compiler/testData/diagnostics/tests/varargs/MostSepcificVarargsWithJava.jet @@ -0,0 +1,19 @@ +// FILE: kotlin.kt +fun main(j : C, s : Array) { + j.from() + j.from("") + j.from("", "") + j.from("", "", "") + + j.from("", *s) // This should not be an ambiguity, see KT-1842 + j.from(*s) +} + +// FILE: C.java +public class C { + void from() {} + void from(String s) {} + void from(String s, String s1) {} + void from(String... s) {} + void from(String s1, String... s) {} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/varargs/kt1835.jet b/compiler/testData/diagnostics/tests/varargs/kt1835.jet new file mode 100644 index 00000000000..05c178d3994 --- /dev/null +++ b/compiler/testData/diagnostics/tests/varargs/kt1835.jet @@ -0,0 +1,12 @@ +// FILE: JavaClass.java +public class JavaClass { + void from(String s) {} + void from(String... s) {} +} + +// FILE: kotlin.kt +fun main(args : Array) { + JavaClass().from() + JavaClass().from("") + JavaClass().from("", "") +} \ No newline at end of file diff --git a/compiler/testData/resolve/varargs/MoreSpecificVarargsOfEqualLength.jet b/compiler/testData/resolve/varargs/MoreSpecificVarargsOfEqualLength.jet new file mode 100644 index 00000000000..d255c714f37 --- /dev/null +++ b/compiler/testData/resolve/varargs/MoreSpecificVarargsOfEqualLength.jet @@ -0,0 +1,9 @@ +fun main(d : D) { + d.`str`from("") + d.`any`from(1) +} + +class D { + ~any~fun from(vararg a : Any){} + ~str~fun from(vararg a : String){} +} \ No newline at end of file