Generalize signature comparison: checking for conflicting overloads.

Also, vararg parameter no longer conflicts with Array<out T>
(CONFLICTING_JVM_DECLARATIONS is reported, can be avoided using @JvmName).
This commit is contained in:
Dmitry Petrov
2016-02-17 18:10:53 +03:00
parent 4ac05f1cd8
commit 8a2b80fe3f
7 changed files with 102 additions and 133 deletions
@@ -21,15 +21,14 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.OverridingUtil.OverrideCompatibilityInfo.Result.INCOMPATIBLE
import org.jetbrains.kotlin.resolve.calls.inference.CallHandle
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystem
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.VALUE_PARAMETER_POSITION
import org.jetbrains.kotlin.resolve.calls.results.FlatSignature
import org.jetbrains.kotlin.resolve.calls.results.SpecificityComparisonCallbacks
import org.jetbrains.kotlin.resolve.calls.results.isSignatureNotLessSpecific
import org.jetbrains.kotlin.resolve.calls.results.varargParameterPosition
import org.jetbrains.kotlin.resolve.descriptorUtil.hasLowPriorityInOverloadResolution
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.singletonOrEmptyList
object OverloadUtil {
@@ -46,52 +45,29 @@ object OverloadUtil {
return checkOverloadability(a, b)
}
private object OverloadabilitySpecificityCallbacks : SpecificityComparisonCallbacks {
override fun isNonSubtypeNotLessSpecific(specific: KotlinType, general: KotlinType): Boolean =
false
}
private fun checkOverloadability(a: CallableDescriptor, b: CallableDescriptor): Boolean {
if (a.hasLowPriorityInOverloadResolution() != b.hasLowPriorityInOverloadResolution()) return true
// NB this makes generic and non-generic declarations with equivalent signatures non-conflicting
// E.g., 'fun <T> foo()' and 'fun foo()'.
// They can be disambiguated by providing explicit type parameters.
if (a.typeParameters.isEmpty() != b.typeParameters.isEmpty()) return true
OverridingUtil.checkReceiverAndParameterCount(a, b)?.let { return it.result == INCOMPATIBLE }
if (ErrorUtils.containsErrorType(a) || ErrorUtils.containsErrorType(b)) return true
if (a.varargParameterPosition() != b.varargParameterPosition()) return true
val aValueParameters = OverridingUtil.compiledValueParameters(a)
val bValueParameters = OverridingUtil.compiledValueParameters(b)
val aSignature = FlatSignature.createFromCallableDescriptor(a)
val bSignature = FlatSignature.createFromCallableDescriptor(b)
val aTypeParameters = a.typeParameters
val bTypeParameters = b.typeParameters
val aIsNotLessSpecificThanB = isSignatureNotLessSpecific(aSignature, bSignature, OverloadabilitySpecificityCallbacks)
val bIsNotLessSpecificThanA = isSignatureNotLessSpecific(bSignature, aSignature, OverloadabilitySpecificityCallbacks)
val avsbConstraintsBuilder: ConstraintSystem.Builder = ConstraintSystemBuilderImpl()
val avsbTypeSubstitutor = avsbConstraintsBuilder.registerTypeVariables(CallHandle.NONE, aTypeParameters)
val bvsaConstraintsBuilder: ConstraintSystem.Builder = ConstraintSystemBuilderImpl()
val bvsaTypeSubstitutor = bvsaConstraintsBuilder.registerTypeVariables(CallHandle.NONE, bTypeParameters)
var constraintIndex = 0
for ((aType, bType) in aValueParameters.zip(bValueParameters)) {
if (aType.isError || bType.isError) return true
if (oneMoreSpecificThanAnother(bType, aType)) return true
if (!TypeUtils.dependsOnTypeParameters(aType, aTypeParameters)
&& !TypeUtils.dependsOnTypeParameters(bType, bTypeParameters)
&& !KotlinTypeChecker.DEFAULT.equalTypes(aType, bType)) {
return true
}
constraintIndex++
val aTypeSubstituted = avsbTypeSubstitutor.safeSubstitute(aType, Variance.INVARIANT)
val bTypeSubstituted = bvsaTypeSubstitutor.safeSubstitute(bType, Variance.INVARIANT)
avsbConstraintsBuilder.addSubtypeConstraint(bType, aTypeSubstituted,
VALUE_PARAMETER_POSITION.position(constraintIndex))
bvsaConstraintsBuilder.addSubtypeConstraint(aType, bTypeSubstituted,
VALUE_PARAMETER_POSITION.position(constraintIndex))
}
if (constraintIndex == 0) return false
avsbConstraintsBuilder.fixVariables()
bvsaConstraintsBuilder.fixVariables()
return avsbConstraintsBuilder.build().status.hasContradiction()
|| bvsaConstraintsBuilder.build().status.hasContradiction()
return !(aIsNotLessSpecificThanB && bIsNotLessSpecificThanA)
}
private enum class DeclarationCategory {
@@ -30,49 +30,50 @@ class FlatSignature<out T>(
val origin: T,
val typeParameters: Collection<TypeParameterDescriptor>,
val valueParameterTypes: List<KotlinType?>,
val hasExtensionReceiver: Boolean,
val hasVarargs: Boolean,
val numDefaults: Int
) {
val isGeneric = typeParameters.isNotEmpty()
companion object {
fun <D : CallableDescriptor, RC : ResolvedCall<D>> createFromResolvedCall(call: RC): FlatSignature<RC> {
val originalDescriptor = call.candidateDescriptor.original
fun <D : CallableDescriptor, RC : ResolvedCall<D>> createFromResolvedCall(resolvedCall: RC): FlatSignature<RC> {
val originalDescriptor = resolvedCall.candidateDescriptor.original
val originalValueParameters = originalDescriptor.valueParameters
val originalTypeParameters = originalDescriptor.typeParameters
var numDefaults = 0
val valueArgumentToParameterType = HashMap<ValueArgument, KotlinType>()
for ((valueParameter, resolvedValueArgument) in call.valueArguments.entries) {
for ((valueParameter, resolvedValueArgument) in resolvedCall.valueArguments.entries) {
if (resolvedValueArgument is DefaultValueArgument) {
numDefaults++
}
else {
val originalValueParameter = originalValueParameters[valueParameter.index]
val parameterType = originalValueParameter.flatType
val parameterType = originalValueParameter.argumentValueType
for (valueArgument in resolvedValueArgument.arguments) {
if (valueArgument.getArgumentExpression() == null) continue
valueArgumentToParameterType[valueArgument] = parameterType
}
}
}
val valueArgumentParameterTypes = call.call.valueArguments.map { valueArgumentToParameterType[it] }
val valueParameterTypes = originalDescriptor.extensionReceiverTypeOrEmpty() + valueArgumentParameterTypes
val hasVarargs = originalDescriptor.valueParameters.any { it.varargElementType != null }
return FlatSignature(call, originalTypeParameters, valueParameterTypes, hasVarargs, numDefaults)
return FlatSignature(resolvedCall,
originalDescriptor.typeParameters,
valueParameterTypes = originalDescriptor.extensionReceiverTypeOrEmpty() +
resolvedCall.call.valueArguments.map { valueArgumentToParameterType[it] },
hasExtensionReceiver = originalDescriptor.extensionReceiverParameter != null,
hasVarargs = originalDescriptor.valueParameters.any { it.varargElementType != null },
numDefaults = numDefaults)
}
fun <D : CallableDescriptor> createFromCallableDescriptor(descriptor: D): FlatSignature<D> =
FlatSignature(descriptor,
descriptor.typeParameters,
valueParameterTypes = descriptor.extensionReceiverTypeOrEmpty() + descriptor.valueParameters.map { it.flatType },
valueParameterTypes = descriptor.extensionReceiverTypeOrEmpty() + descriptor.valueParameters.map { it.argumentValueType },
hasExtensionReceiver = descriptor.extensionReceiverParameter != null,
hasVarargs = descriptor.valueParameters.any { it.varargElementType != null },
numDefaults = 0)
val ValueParameterDescriptor.flatType: KotlinType
val ValueParameterDescriptor.argumentValueType: KotlinType
get() = varargElementType ?: type
fun CallableDescriptor.extensionReceiverTypeOrEmpty() =
@@ -23,52 +23,47 @@ import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.valuePara
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
interface SpecificityComparisonCallbacks<T> {
fun isNotLessSpecificSignature(signature1: FlatSignature<T>, signature2: FlatSignature<T>): Boolean?
fun isTypeNotLessSpecific(type1: KotlinType, type2: KotlinType): Boolean?
interface SpecificityComparisonCallbacks {
fun isNonSubtypeNotLessSpecific(specific: KotlinType, general: KotlinType): Boolean
}
fun <T> isSignatureNotLessSpecific(
signature1: FlatSignature<T>,
signature2: FlatSignature<T>,
callHandle: CallHandle,
callbacks: SpecificityComparisonCallbacks<T>
specific: FlatSignature<T>,
general: FlatSignature<T>,
callbacks: SpecificityComparisonCallbacks,
callHandle: CallHandle = CallHandle.NONE
): Boolean {
callbacks.isNotLessSpecificSignature(signature1, signature2)?.let { return it }
if (specific.hasExtensionReceiver != general.hasExtensionReceiver) return false
if (specific.valueParameterTypes.size != general.valueParameterTypes.size) return false
val typeParameters = signature2.typeParameters
val typeParameters = general.typeParameters
val constraintSystemBuilder: ConstraintSystem.Builder = ConstraintSystemBuilderImpl()
var numConstraints = 0
val typeSubstitutor = constraintSystemBuilder.registerTypeVariables(callHandle, typeParameters)
for ((type1, type2) in signature1.valueParameterTypes.zip(signature2.valueParameterTypes)) {
if (type1 == null || type2 == null) continue
var numConstraints = 0
for ((specificType, generalType) in specific.valueParameterTypes.zip(general.valueParameterTypes)) {
if (specificType == null || generalType == null) continue
if (isDefinitelyLessSpecificByTypeSpecificity(type1, type2)) {
if (isDefinitelyLessSpecificByTypeSpecificity(specificType, generalType)) {
return false
}
if (typeParameters.isEmpty() || !TypeUtils.dependsOnTypeParameters(type2, typeParameters)) {
if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(type1, type2)) {
callbacks.isTypeNotLessSpecific(type1, type2)?.let { if (!it) return false }
if (typeParameters.isEmpty() || !TypeUtils.dependsOnTypeParameters(generalType, typeParameters)) {
if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(specificType, generalType)) {
if (!callbacks.isNonSubtypeNotLessSpecific(specificType, generalType)) {
return false
}
}
}
else {
val constraintPosition = valueParameterPosition(numConstraints++)
val substitutedType2 = typeSubstitutor.safeSubstitute(type2, Variance.INVARIANT)
constraintSystemBuilder.addSubtypeConstraint(type1, substitutedType2, constraintPosition)
val substitutedGeneralType = typeSubstitutor.safeSubstitute(generalType, Variance.INVARIANT)
constraintSystemBuilder.addSubtypeConstraint(specificType, substitutedGeneralType, valueParameterPosition(numConstraints++))
}
}
if (numConstraints > 0) {
constraintSystemBuilder.fixVariables()
val constraintSystem = constraintSystemBuilder.build()
if (constraintSystem.status.hasContradiction()) {
return false
}
}
return true
constraintSystemBuilder.fixVariables()
val constraintSystem = constraintSystemBuilder.build()
return !constraintSystem.status.hasContradiction()
}
private fun isDefinitelyLessSpecificByTypeSpecificity(specific: KotlinType, general: KotlinType): Boolean {
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
import org.jetbrains.kotlin.resolve.calls.inference.CallHandle
import org.jetbrains.kotlin.resolve.calls.inference.toHandle
import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -147,18 +146,22 @@ class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
call1: FlatSignature<MutableResolvedCall<D>>,
call2: FlatSignature<MutableResolvedCall<D>>,
discriminateGenerics: Boolean
): Boolean =
isSignatureNotLessSpecific(call1, call2, call1.callHandle(),
if (discriminateGenerics)
SpecificityComparisonDiscriminatingGenerics
else
DefaultCallSpecificityComparison)
): Boolean {
if (discriminateGenerics) {
val isGeneric1 = call1.isGeneric
val isGeneric2 = call2.isGeneric
// generic loses to non-generic
if (isGeneric1 && !isGeneric2) return false
if (!isGeneric1 && isGeneric2) return true
// two generics are non-comparable
if (isGeneric1 && isGeneric2) return false
}
private abstract inner class SpecificityComparisonWithNumerics<T> : SpecificityComparisonCallbacks<T> {
override fun isNotLessSpecificSignature(signature1: FlatSignature<T>, signature2: FlatSignature<T>): Boolean?
= null
return isSignatureNotLessSpecific(call1, call2, SpecificityComparisonWithNumerics, call1.callHandle())
}
override fun isTypeNotLessSpecific(type1: KotlinType, type2: KotlinType): Boolean? {
private val SpecificityComparisonWithNumerics = object : SpecificityComparisonCallbacks {
override fun isNonSubtypeNotLessSpecific(specific: KotlinType, general: KotlinType): Boolean {
val _double = builtIns.doubleType
val _float = builtIns.floatType
val _long = builtIns.longType
@@ -167,41 +170,21 @@ class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
val _short = builtIns.shortType
when {
TypeUtils.equalTypes(type1, _double) && TypeUtils.equalTypes(type2, _float) -> return true
TypeUtils.equalTypes(type1, _int) -> {
TypeUtils.equalTypes(specific, _double) && TypeUtils.equalTypes(general, _float) -> return true
TypeUtils.equalTypes(specific, _int) -> {
when {
TypeUtils.equalTypes(type2, _long) -> return true
TypeUtils.equalTypes(type2, _byte) -> return true
TypeUtils.equalTypes(type2, _short) -> return true
TypeUtils.equalTypes(general, _long) -> return true
TypeUtils.equalTypes(general, _byte) -> return true
TypeUtils.equalTypes(general, _short) -> return true
}
}
TypeUtils.equalTypes(type1, _short) && TypeUtils.equalTypes(type2, _byte) -> return true
TypeUtils.equalTypes(specific, _short) && TypeUtils.equalTypes(general, _byte) -> return true
}
return false
}
}
private val DefaultCallSpecificityComparison = object : SpecificityComparisonWithNumerics<MutableResolvedCall<*>>() {}
private val DefaultDescriptorSpecificityComparison = object : SpecificityComparisonWithNumerics<CallableDescriptor>() {}
private val SpecificityComparisonDiscriminatingGenerics = object : SpecificityComparisonWithNumerics<MutableResolvedCall<*>>() {
override fun isNotLessSpecificSignature(
signature1: FlatSignature<MutableResolvedCall<*>>,
signature2: FlatSignature<MutableResolvedCall<*>>
): Boolean? {
val isGeneric1 = signature1.isGeneric
val isGeneric2 = signature2.isGeneric
// generic loses to non-generic
if (isGeneric1 && !isGeneric2) return false
if (!isGeneric1 && isGeneric2) return true
// two generics are non-comparable
if (isGeneric1 && isGeneric2) return false
// otherwise continue comparing signatures
return null
}
}
private fun <D: CallableDescriptor> isOfNotLessSpecificShape(
call1: FlatSignature<MutableResolvedCall<D>>,
call2: FlatSignature<MutableResolvedCall<D>>
@@ -250,23 +233,23 @@ class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
}
/**
* Returns `true` if `d1` is definitely not less specific than `d2`,
* `false` if `d1` is definitely less specific than `d2`,
* Returns `true` if `f` is definitely not less specific than `g`,
* `false` if `f` is definitely less specific than `g`,
* `null` if undecided.
*/
private fun compareFunctionParameterTypes(f: CallableDescriptor, g: CallableDescriptor): Boolean {
private fun isNotLessSpecificCallableReferenceDescriptor(f: CallableDescriptor, g: CallableDescriptor): Boolean {
if (f.valueParameters.size != g.valueParameters.size) return false
if (f.varargParameterPosition() != g.varargParameterPosition()) return false
val fSignature = FlatSignature.createFromCallableDescriptor(f)
val gSignature = FlatSignature.createFromCallableDescriptor(g)
return isSignatureNotLessSpecific(fSignature, gSignature, CallHandle.NONE, DefaultDescriptorSpecificityComparison)
return isSignatureNotLessSpecific(fSignature, gSignature, SpecificityComparisonWithNumerics)
}
private fun isNotLessSpecificCallableReference(f: CallableDescriptor, g: CallableDescriptor): Boolean =
// TODO should we "discriminate generic descriptors" for callable references?
tryCompareDescriptorsFromScripts(f, g) ?:
compareFunctionParameterTypes(f, g)
isNotLessSpecificCallableReferenceDescriptor(f, g)
companion object {
// Different smartcasts may lead to the same candidate descriptor wrapped into different ResolvedCallImpl objects
@@ -1,5 +1,8 @@
<!CONFLICTING_OVERLOADS!>fun foo(vararg <!UNUSED_PARAMETER!>x<!>: Int)<!> {}
<!CONFLICTING_OVERLOADS!>fun foo(<!UNUSED_PARAMETER!>x<!>: IntArray)<!> {}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo(vararg <!UNUSED_PARAMETER!>x<!>: Int)<!> {}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo(<!UNUSED_PARAMETER!>x<!>: IntArray)<!> {}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo(vararg <!UNUSED_PARAMETER!>x<!>: Int?)<!> {}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo(<!UNUSED_PARAMETER!>x<!>: Array<Int>)<!> {}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo(<!UNUSED_PARAMETER!>x<!>: Array<Int>)<!> {}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo(vararg <!UNUSED_PARAMETER!>nn<!>: Number)<!> {}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo(<!UNUSED_PARAMETER!>nn<!>: Array<out Number>)<!> {}
@@ -2,5 +2,7 @@ package
public fun foo(/*0*/ x: kotlin.Array<kotlin.Int>): kotlin.Unit
public fun foo(/*0*/ vararg x: kotlin.Int? /*kotlin.Array<out kotlin.Int?>*/): kotlin.Unit
public fun foo(/*0*/ nn: kotlin.Array<out kotlin.Number>): kotlin.Unit
public fun foo(/*0*/ vararg nn: kotlin.Number /*kotlin.Array<out kotlin.Number>*/): kotlin.Unit
public fun foo(/*0*/ vararg x: kotlin.Int /*kotlin.IntArray*/): kotlin.Unit
public fun foo(/*0*/ x: kotlin.IntArray): kotlin.Unit
@@ -136,6 +136,15 @@ public class ErrorUtils {
};
}
public static boolean containsErrorType(@NotNull CallableDescriptor callableDescriptor) {
if (callableDescriptor instanceof FunctionDescriptor) {
return containsErrorType((FunctionDescriptor) callableDescriptor);
}
else {
return containsErrorType(callableDescriptor.getReturnType());
}
}
public static boolean containsErrorType(@NotNull FunctionDescriptor function) {
if (containsErrorType(function.getReturnType())) {
return true;