Use constraint system for comparing for specificity against a generic signature.

Drop "discrimiate generics" mode where it's unneeded.
This commit is contained in:
Dmitry Petrov
2015-12-25 15:20:43 +03:00
parent aeefdffaab
commit 02daeac41b
14 changed files with 233 additions and 79 deletions

View File

@@ -24,43 +24,42 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystem
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPosition
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind
import org.jetbrains.kotlin.resolve.calls.inference.toHandle
import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionMutableResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Specificity
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.getSpecificityRelationTo
import org.jetbrains.kotlin.utils.addToStdlib.check
class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
fun <D : CallableDescriptor> findMaximallySpecific(
candidates: Set<MutableResolvedCall<D>>,
discriminateGenericDescriptors: Boolean,
checkArgumentsMode: CheckArgumentTypesMode
checkArgumentsMode: CheckArgumentTypesMode,
discriminateGenerics: Boolean
): MutableResolvedCall<D>? =
if (candidates.size <= 1)
candidates.firstOrNull()
else when (checkArgumentsMode) {
CheckArgumentTypesMode.CHECK_CALLABLE_TYPE ->
uniquifyCandidatesSet(candidates).filter {
isMaxSpecific(it, candidates) {
isDefinitelyMostSpecific(it, candidates) {
call1, call2 ->
isNotLessSpecificCallableReference(call1.resultingDescriptor, call2.resultingDescriptor)
}
}.singleOrNull()
CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS ->
findMaximallySpecificCall(candidates, discriminateGenericDescriptors)
findMaximallySpecificCall(candidates, discriminateGenerics)
}
fun <D : CallableDescriptor> findMaximallySpecificVariableAsFunctionCalls(
candidates: Set<MutableResolvedCall<D>>,
discriminateGenericDescriptors: Boolean
): Set<MutableResolvedCall<D>> {
fun <D : CallableDescriptor> findMaximallySpecificVariableAsFunctionCalls(candidates: Set<MutableResolvedCall<D>>): Set<MutableResolvedCall<D>> {
val variableCalls = candidates.mapTo(newResolvedCallSet<MutableResolvedCall<VariableDescriptor>>(candidates.size)) {
if (it is VariableAsFunctionMutableResolvedCall)
it.variableCall
@@ -68,17 +67,14 @@ class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
throw AssertionError("Regular call among variable-as-function calls: $it")
}
val maxSpecificVariableCall = findMaximallySpecificCall(variableCalls, discriminateGenericDescriptors) ?: return emptySet()
val maxSpecificVariableCall = findMaximallySpecificCall(variableCalls, false) ?: return emptySet()
return candidates.filterTo(newResolvedCallSet<MutableResolvedCall<D>>(2)) {
it.resultingVariableDescriptor == maxSpecificVariableCall.resultingDescriptor
}
}
private fun <D : CallableDescriptor> findMaximallySpecificCall(
candidates: Set<MutableResolvedCall<D>>,
discriminateGenericDescriptors: Boolean
): MutableResolvedCall<D>? {
private fun <D : CallableDescriptor> findMaximallySpecificCall(candidates: Set<MutableResolvedCall<D>>, discriminateGenerics: Boolean): MutableResolvedCall<D>? {
val filteredCandidates = uniquifyCandidatesSet(candidates)
if (filteredCandidates.size <= 1) return filteredCandidates.singleOrNull()
@@ -88,29 +84,41 @@ class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
CandidateCallWithArgumentMapping.create(candidateCall) { it.arguments.filter { it.getArgumentExpression() != null } }
}
val mostSpecificCandidates = conflictingCandidates.selectMostSpecificCallsWithArgumentMapping(discriminateGenericDescriptors)
val bestCandidatesByParameterTypes = conflictingCandidates.mapNotNull {
candidate ->
candidate.check {
isMostSpecific(candidate, conflictingCandidates) {
call1, call2 ->
isNotLessSpecificCallWithArgumentMapping(call1, call2, discriminateGenerics)
}
}
}
return mostSpecificCandidates.singleOrNull()
return bestCandidatesByParameterTypes.exactMaxWith { call1, call2 -> isOfNotLessSpecificShape(call1, call2) }?.resolvedCall
}
private fun <D : CallableDescriptor, K> Collection<CandidateCallWithArgumentMapping<D, K>>.selectMostSpecificCallsWithArgumentMapping(
discriminateGenericDescriptors: Boolean
): Collection<MutableResolvedCall<D>> =
this.mapNotNull {
candidate ->
candidate.check {
isMaxSpecific(candidate, this) {
call1, call2 ->
isNotLessSpecificCallWithArgumentMapping(call1, call2, discriminateGenericDescriptors)
}
}?.resolvedCall
private inline fun <C : Any> Collection<C>.exactMaxWith(isNotWorse: (C, C) -> Boolean): C? {
var result: C? = null
for (candidate in this) {
if (result == null || isNotWorse(candidate, result)) {
result = candidate
}
}
if (result == null) return null
if (any { it != result && isNotWorse(it, result!!) }) {
return null
}
return result
}
private inline fun <C> isMostSpecific(candidate: C, candidates: Collection<C>, isNotLessSpecific: (C, C) -> Boolean): Boolean =
candidates.all {
other ->
candidate === other ||
isNotLessSpecific(candidate, other)
}
private inline fun <C> isMaxSpecific(
candidate: C,
candidates: Collection<C>,
isNotLessSpecific: (C, C) -> Boolean
): Boolean =
private inline fun <C> isDefinitelyMostSpecific(candidate: C, candidates: Collection<C>, isNotLessSpecific: (C, C) -> Boolean): Boolean =
candidates.all {
other ->
candidate === other ||
@@ -123,53 +131,90 @@ class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
private fun <D : CallableDescriptor, K> isNotLessSpecificCallWithArgumentMapping(
call1: CandidateCallWithArgumentMapping<D, K>,
call2: CandidateCallWithArgumentMapping<D, K>,
discriminateGenericDescriptors: Boolean
discriminateGenerics: Boolean
): Boolean {
return tryCompareDescriptorsFromScripts(call1.resultingDescriptor, call2.resultingDescriptor) ?:
compareCallsWithArgumentMapping(call1, call2, discriminateGenericDescriptors)
compareCallsByUsedArguments(call1, call2, discriminateGenerics)
}
/**
* Returns `true` if `d1` is definitely not less specific than `d2`,
* `false` otherwise.
*/
private fun <D : CallableDescriptor, K> compareCallsWithArgumentMapping(
private fun <D : CallableDescriptor, K> compareCallsByUsedArguments(
call1: CandidateCallWithArgumentMapping<D, K>,
call2: CandidateCallWithArgumentMapping<D, K>,
discriminateGenericDescriptors: Boolean
discriminateGenerics: Boolean
): Boolean {
val substituteParameterTypes =
if (discriminateGenericDescriptors) {
if (!call1.isGeneric && call2.isGeneric) return true
if (call1.isGeneric && !call2.isGeneric) return false
call1.isGeneric && call2.isGeneric
}
else false
val extensionReceiverType1 = call1.getExtensionReceiverType(substituteParameterTypes)
val extensionReceiverType2 = call2.getExtensionReceiverType(substituteParameterTypes)
tryCompareExtensionReceiverType(extensionReceiverType1, extensionReceiverType2)?.let {
return it
if (discriminateGenerics) {
val isGeneric1 = call1.isGeneric
val isGeneric2 = call2.isGeneric
if (isGeneric1 && !isGeneric2) return false
if (!isGeneric1 && isGeneric2) return true
if (isGeneric1 && isGeneric2) return false
}
val hasVarargs1 = call1.resultingDescriptor.hasVarargs
val hasVarargs2 = call2.resultingDescriptor.hasVarargs
if (hasVarargs1 && !hasVarargs2) return false
if (!hasVarargs1 && hasVarargs2) return true
val typeParameters = call2.resolvedCall.resultingDescriptor.typeParameters
val constraintSystemBuilder: ConstraintSystem.Builder = ConstraintSystemBuilderImpl()
var hasConstraints = false
val typeSubstitutor = constraintSystemBuilder.registerTypeVariables(call1.resolvedCall.call.toHandle(), typeParameters)
fun compareTypesAndUpdateConstraints(type1: KotlinType?, type2: KotlinType?, constraintPosition: ConstraintPosition): Boolean {
if (type1 == null || type2 == null) return true
if (typeParameters.isEmpty() || !TypeUtils.dependsOnTypeParameters(type2, typeParameters)) {
if (!typeNotLessSpecific(type1, type2)) {
return false
}
}
else {
val substitutedType2 = typeSubstitutor.safeSubstitute(type2, Variance.INVARIANT)
constraintSystemBuilder.addSubtypeConstraint(type1, substitutedType2, constraintPosition)
hasConstraints = true
}
return true
}
val extensionReceiverType1 = call1.getExtensionReceiverType(false)
val extensionReceiverType2 = call2.getExtensionReceiverType(false)
if (!compareTypesAndUpdateConstraints(extensionReceiverType1, extensionReceiverType2, ConstraintPositionKind.RECEIVER_POSITION.position())) {
return false
}
assert(call1.argumentsCount == call2.argumentsCount) {
"$call1 and $call2 have different number of explicit arguments"
}
var index = 0
for (argumentKey in call1.argumentKeys) {
val type1 = call1.getValueParameterType(argumentKey, substituteParameterTypes) ?: continue
val type2 = call2.getValueParameterType(argumentKey, substituteParameterTypes) ?: continue
val type1 = call1.getValueParameterType(argumentKey, false)
val type2 = call2.getValueParameterType(argumentKey, false)
if (!typeNotLessSpecific(type1, type2)) {
if (!compareTypesAndUpdateConstraints(type1, type2, ConstraintPositionKind.VALUE_PARAMETER_POSITION.position(index++))) {
return false
}
}
if (hasConstraints) {
constraintSystemBuilder.fixVariables()
val constraintSystem = constraintSystemBuilder.build()
if (constraintSystem.status.hasContradiction()) {
return false
}
}
return true
}
private fun <D: CallableDescriptor, K> isOfNotLessSpecificShape(
call1: CandidateCallWithArgumentMapping<D, K>,
call2: CandidateCallWithArgumentMapping<D, K>
): Boolean {
val hasVarargs1 = call1.resultingDescriptor.hasVarargs
val hasVarargs2 = call2.resultingDescriptor.hasVarargs
if (hasVarargs1 && !hasVarargs2) return false
if (!hasVarargs1 && hasVarargs2) return true
if (call1.parametersWithDefaultValuesCount > call2.parametersWithDefaultValuesCount) {
return false
}

View File

@@ -195,7 +195,7 @@ public class ResolutionResultsHandler {
}
if (candidates.iterator().next() instanceof VariableAsFunctionResolvedCall) {
candidates = overloadingConflictResolver.findMaximallySpecificVariableAsFunctionCalls(candidates, discriminateGenerics);
candidates = overloadingConflictResolver.findMaximallySpecificVariableAsFunctionCalls(candidates);
}
Set<MutableResolvedCall<D>> noOverrides = OverrideResolver.filterOutOverridden(candidates, MAP_TO_RESULT);
@@ -203,14 +203,14 @@ public class ResolutionResultsHandler {
return OverloadResolutionResultsImpl.success(noOverrides.iterator().next());
}
MutableResolvedCall<D> maximallySpecific = overloadingConflictResolver.findMaximallySpecific(noOverrides, false, checkArgumentsMode);
MutableResolvedCall<D> maximallySpecific = overloadingConflictResolver.findMaximallySpecific(noOverrides, checkArgumentsMode, false);
if (maximallySpecific != null) {
return OverloadResolutionResultsImpl.success(maximallySpecific);
}
if (discriminateGenerics) {
MutableResolvedCall<D> maximallySpecificGenericsDiscriminated = overloadingConflictResolver.findMaximallySpecific(
noOverrides, true, checkArgumentsMode);
noOverrides, checkArgumentsMode, true);
if (maximallySpecificGenericsDiscriminated != null) {
return OverloadResolutionResultsImpl.success(maximallySpecificGenericsDiscriminated);
}

View File

@@ -51,9 +51,9 @@ fun test() {
h checkType { _<Int>() }
<!OVERLOAD_RESOLUTION_AMBIGUITY!>wrongTwoDefault<!>(1)
wrongTwoDefault(1)
<!CANNOT_COMPLETE_RESOLVE!>wrongWithDefaultGeneric<!>("")
wrongWithDefaultGeneric("")
<!UNREACHABLE_CODE!><!OVERLOAD_RESOLUTION_AMBIGUITY!>wrong<!>(<!>null!!<!UNREACHABLE_CODE!>)<!>
}

View File

@@ -0,0 +1,9 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
object X1
object X2
fun <T1> foo(x: T1, f: (T1) -> T1) = X1
fun <T2> foo(xf: () -> T2, f: (T2) -> T2) = X2
val test: X2 = <!CANNOT_COMPLETE_RESOLVE!>foo<!>({ 0 }, { <!CANNOT_INFER_PARAMETER_TYPE!>it<!> -> <!DEBUG_INFO_ELEMENT_WITH_ERROR_TYPE!>it<!> <!DEBUG_INFO_ELEMENT_WITH_ERROR_TYPE!>+<!> 1 })

View File

@@ -0,0 +1,19 @@
package
public val test: X2
public fun </*0*/ T2> foo(/*0*/ xf: () -> T2, /*1*/ f: (T2) -> T2): X2
public fun </*0*/ T1> foo(/*0*/ x: T1, /*1*/ f: (T1) -> T1): X1
public object X1 {
private constructor X1()
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object X2 {
private constructor X2()
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}

View File

@@ -0,0 +1,9 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
object Foo1
object Foo2
fun foo(vararg ss: String) = Foo1
fun foo(x: Any) = Foo2
val test1: Foo1 = foo("")

View File

@@ -0,0 +1,19 @@
package
public val test1: Foo1
public fun foo(/*0*/ x: kotlin.Any): Foo2
public fun foo(/*0*/ vararg ss: kotlin.String /*kotlin.Array<out kotlin.String>*/): Foo1
public object Foo1 {
private constructor Foo1()
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object Foo2 {
private constructor Foo2()
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}

View File

@@ -6,7 +6,7 @@ object X2
fun overloadedFun5(vararg ss: String) = X1
fun overloadedFun5(s: String, vararg ss: String) = X2
val test1: X1 = <!OVERLOAD_RESOLUTION_AMBIGUITY!>overloadedFun5<!>("")
val test1 = <!OVERLOAD_RESOLUTION_AMBIGUITY!>overloadedFun5<!>("")
val test2 = <!OVERLOAD_RESOLUTION_AMBIGUITY!>overloadedFun5<!>("", "")
val test3: X2 = overloadedFun5(s = "", ss = "")
val test4: X1 = overloadedFun5(ss = "")

View File

@@ -1,6 +1,6 @@
package
public val test1: X1
public val test1: [ERROR : Type for overloadedFun5("")]
public val test2: [ERROR : Type for overloadedFun5("", "")]
public val test3: X2
public val test4: X1

View File

@@ -3,10 +3,10 @@
import java.util.*
public class J {
public static <E extends Enum<E>> String foo(E e) { return ""; }
public static <E extends Enum<E>> String foo(E e1, E e2) { return ""; }
public static <E extends Enum<E>> String foo(E s1, E s2, E s3) { return ""; }
public static <E extends Enum<E>> int foo(E... ss) { return 0; }
public static <E1 extends Enum<E1>> String foo(E1 e) { return ""; }
public static <E2 extends Enum<E2>> String foo(E2 e1, E2 e2) { return ""; }
public static <E3 extends Enum<E3>> String foo(E3 s1, E3 s2, E3 s3) { return ""; }
public static <E4 extends Enum<E4>> int foo(E4... ss) { return 0; }
}
// FILE: test.kt

View File

@@ -15,10 +15,10 @@ public open class J {
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public open fun </*0*/ E : kotlin.Enum<E!>!> foo(/*0*/ e: E!): kotlin.String!
public open fun </*0*/ E : kotlin.Enum<E!>!> foo(/*0*/ e1: E!, /*1*/ e2: E!): kotlin.String!
public open fun </*0*/ E : kotlin.Enum<E!>!> foo(/*0*/ s1: E!, /*1*/ s2: E!, /*2*/ s3: E!): kotlin.String!
public open fun </*0*/ E : kotlin.Enum<E!>!> foo(/*0*/ vararg ss: E! /*kotlin.Array<(out) E!>!*/): kotlin.Int
public open fun </*0*/ E1 : kotlin.Enum<E1!>!> foo(/*0*/ e: E1!): kotlin.String!
public open fun </*0*/ E2 : kotlin.Enum<E2!>!> foo(/*0*/ e1: E2!, /*1*/ e2: E2!): kotlin.String!
public open fun </*0*/ E3 : kotlin.Enum<E3!>!> foo(/*0*/ s1: E3!, /*1*/ s2: E3!, /*2*/ s3: E3!): kotlin.String!
public open fun </*0*/ E4 : kotlin.Enum<E4!>!> foo(/*0*/ vararg ss: E4! /*kotlin.Array<(out) E4!>!*/): kotlin.Int
}
public final enum class X : kotlin.Enum<X> {

View File

@@ -0,0 +1,9 @@
object X1
object X2
class A<T>
fun <T1> A<T1>.foo() = X1
fun <T2> A<out T2>.foo() = X2
fun <T> A<out T>.test() = <!CANNOT_COMPLETE_RESOLVE!>foo<!>()

View File

@@ -0,0 +1,26 @@
package
public fun </*0*/ T1> A<T1>.foo(): X1
public fun </*0*/ T2> A<out T2>.foo(): X2
public fun </*0*/ T> A<out T>.test(): [ERROR : Error function type]
public final class A</*0*/ T> {
public constructor A</*0*/ T>()
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object X1 {
private constructor X1()
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object X2 {
private constructor X2()
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}

View File

@@ -13892,15 +13892,15 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/resolve/overloadConflicts"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("extensionReceiverAndVarargs.kt")
public void testExtensionReceiverAndVarargs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/extensionReceiverAndVarargs.kt");
@TestMetadata("allLambdas.kt")
public void testAllLambdas() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/allLambdas.kt");
doTest(fileName);
}
@TestMetadata("javaOverloadedVarargs.kt")
public void testJavaOverloadedVarargs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/javaOverloadedVarargs.kt");
@TestMetadata("extensionReceiverAndVarargs.kt")
public void testExtensionReceiverAndVarargs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/extensionReceiverAndVarargs.kt");
doTest(fileName);
}
@@ -13922,6 +13922,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("varargWithMoreSpecificSignature.kt")
public void testVarargWithMoreSpecificSignature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/varargWithMoreSpecificSignature.kt");
doTest(fileName);
}
@TestMetadata("varargs.kt")
public void testVarargs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/varargs.kt");
@@ -13939,6 +13945,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/varargsMixed.kt");
doTest(fileName);
}
@TestMetadata("varargsWithRecursiveGenerics.kt")
public void testVarargsWithRecursiveGenerics() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/varargsWithRecursiveGenerics.kt");
doTest(fileName);
}
@TestMetadata("withVariance.kt")
public void testWithVariance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts/withVariance.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/diagnostics/tests/resolve/priority")